+
+
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
- |
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+ | |
+
+
-
+
+
+
+
+ test datasource
+
+
+ |
+
+
+
+
+
-
+ |
+
+ |
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
- No items found
+ test datasource2
|
@@ -2177,6 +1753,365 @@ exports[`DataSourceTable should get datasources successful should render normall
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+ 10
+
+ }
+ closePopover={[Function]}
+ display="inlineBlock"
+ hasArrow={true}
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="none"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2184,18 +2119,6 @@ exports[`DataSourceTable should get datasources successful should render normall
-
-
-
`;
diff --git a/src/plugins/data_source_management/public/components/data_source_table/data_source_table.test.tsx b/src/plugins/data_source_management/public/components/data_source_table/data_source_table.test.tsx
index 282e8878b629..cb30ffdc7a0b 100644
--- a/src/plugins/data_source_management/public/components/data_source_table/data_source_table.test.tsx
+++ b/src/plugins/data_source_management/public/components/data_source_table/data_source_table.test.tsx
@@ -16,7 +16,6 @@ import { OpenSearchDashboardsContextProvider } from '../../../../opensearch_dash
import { getMappedDataSources, mockManagementPlugin } from '../../mocks';
const deleteButtonIdentifier = '[data-test-subj="deleteDataSourceConnections"]';
-const toastsIdentifier = 'EuiGlobalToastList';
const tableIdentifier = 'EuiInMemoryTable';
const confirmModalIndentifier = 'EuiConfirmModal';
const tableColumnHeaderIdentifier = 'EuiTableHeaderCell';
@@ -26,7 +25,6 @@ describe('DataSourceTable', () => {
const mockedContext = mockManagementPlugin.createDataSourceManagementContext();
let component: ReactWrapper, React.Component<{}, {}, any>>;
const history = (scopedHistoryMock.create() as unknown) as ScopedHistory;
-
describe('should get datasources failed', () => {
beforeEach(async () => {
spyOn(utils, 'getDataSources').and.returnValue(Promise.reject({}));
@@ -48,24 +46,6 @@ describe('DataSourceTable', () => {
);
});
});
-
- it('should show toast and remove toast normally', () => {
- expect(component).toMatchSnapshot();
- expect(utils.getDataSources).toHaveBeenCalled();
- component.update();
- // @ts-ignore
- expect(component.find(toastsIdentifier).props().toasts.length).toBe(1);
-
- act(() => {
- // @ts-ignore
- component.find(toastsIdentifier).first().prop('dismissToast')({
- id: 'dataSourcesManagement.dataSourceListing.fetchDataSourceFailMsg',
- });
- });
- component.update();
- // @ts-ignore
- expect(component.find(toastsIdentifier).props().toasts.length).toBe(0); // failure toast
- });
});
describe('should get datasources successful', () => {
@@ -88,6 +68,7 @@ describe('DataSourceTable', () => {
}
);
});
+ component.update();
});
it('should render normally', () => {
@@ -153,7 +134,7 @@ describe('DataSourceTable', () => {
expect(component.find(confirmModalIndentifier).exists()).toBe(false);
});
- it('should show toast when delete datasources failed', async () => {
+ it('should delete datasources & fail', async () => {
spyOn(utils, 'deleteMultipleDataSources').and.returnValue(Promise.reject({}));
act(() => {
// @ts-ignore
@@ -171,7 +152,6 @@ describe('DataSourceTable', () => {
component.update();
expect(utils.deleteMultipleDataSources).toHaveBeenCalled();
// @ts-ignore
- expect(component.find(toastsIdentifier).props().toasts.length).toBe(1);
expect(component.find(confirmModalIndentifier).exists()).toBe(false);
});
});
diff --git a/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx b/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx
index 97551c62ee36..f4f537436e05 100644
--- a/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx
+++ b/src/plugins/data_source_management/public/components/data_source_table/data_source_table.tsx
@@ -9,18 +9,17 @@ import {
EuiConfirmModal,
EuiFlexGroup,
EuiFlexItem,
- EuiGlobalToastList,
- EuiGlobalToastListToast,
EuiInMemoryTable,
EuiPageContent,
+ EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import React, { useState } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
-import { FormattedMessage } from '@osd/i18n/react';
import { useEffectOnce } from 'react-use';
+import { i18n } from '@osd/i18n';
import { getListBreadcrumbs } from '../breadcrumbs';
import {
reactRouterNavigate,
@@ -31,16 +30,17 @@ import { CreateButton } from '../create_button';
import { deleteMultipleDataSources, getDataSources } from '../utils';
import { LoadingMask } from '../loading_mask';
import {
- cancelText,
- deleteText,
- dsListingAriaRegion,
- dsListingDeleteDataSourceConfirmation,
- dsListingDeleteDataSourceDescription,
- dsListingDeleteDataSourceTitle,
- dsListingDeleteDataSourceWarning,
- dsListingDescription,
- dsListingPageTitle,
- dsListingTitle,
+ CANCEL_TEXT,
+ DELETE_TEXT,
+ DS_LISTING_ARIA_REGION,
+ DS_LISTING_DATA_SOURCE_DELETE_ACTION,
+ DS_LISTING_DATA_SOURCE_DELETE_IMPACT,
+ DS_LISTING_DATA_SOURCE_DELETE_WARNING,
+ DS_LISTING_DATA_SOURCE_MULTI_DELETE_TITLE,
+ DS_LISTING_DESCRIPTION,
+ DS_LISTING_NO_DATA,
+ DS_LISTING_PAGE_TITLE,
+ DS_LISTING_TITLE,
} from '../text_content/text_content';
/* Table config */
@@ -56,17 +56,17 @@ const sorting = {
},
};
-const toastLifeTimeMs = 6000;
-
export const DataSourceTable = ({ history }: RouteComponentProps) => {
- const { chrome, setBreadcrumbs, savedObjects } = useOpenSearchDashboards<
- DataSourceManagementContext
- >().services;
+ const {
+ chrome,
+ setBreadcrumbs,
+ savedObjects,
+ notifications: { toasts },
+ } = useOpenSearchDashboards().services;
/* Component state variables */
const [dataSources, setDataSources] = useState([]);
const [selectedDataSources, setSelectedDataSources] = useState([]);
- const [toasts, setToasts] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(false);
const [isDeleting, setIsDeleting] = React.useState(false);
const [confirmDeleteVisible, setConfirmDeleteVisible] = React.useState(false);
@@ -77,7 +77,7 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
setBreadcrumbs(getListBreadcrumbs());
/* Browser - Page Title */
- chrome.docTitle.change(dsListingPageTitle);
+ chrome.docTitle.change(DS_LISTING_PAGE_TITLE);
/* fetch data sources*/
fetchDataSources();
@@ -93,10 +93,7 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
setDataSources([]);
handleDisplayToastMessage({
id: 'dataSourcesManagement.dataSourceListing.fetchDataSourceFailMsg',
- defaultMessage:
- 'Error occurred while fetching the records for Data sources. Please try it again',
- color: 'warning',
- iconType: 'alert',
+ defaultMessage: 'Error occurred while fetching the records for Data sources.',
});
})
.finally(() => {
@@ -109,14 +106,13 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
return (
{
setConfirmDeleteVisible(true);
}}
data-test-subj="deleteDataSourceConnections"
disabled={selectedDataSources.length === 0}
>
- Delete {selectedDataSources.length || ''} connection
+ Delete {selectedDataSources.length || ''} {selectedDataSources.length ? 'connection' : ''}
{selectedDataSources.length >= 2 ? 's' : ''}
);
@@ -180,7 +176,7 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
const tableRenderDeleteModal = () => {
return confirmDeleteVisible ? (
{
setConfirmDeleteVisible(false);
}}
@@ -188,13 +184,13 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
setConfirmDeleteVisible(false);
onClickDelete();
}}
- cancelButtonText={cancelText}
- confirmButtonText={deleteText}
+ cancelButtonText={CANCEL_TEXT}
+ confirmButtonText={DELETE_TEXT}
defaultFocusedButton="confirm"
>
- {dsListingDeleteDataSourceDescription}
- {dsListingDeleteDataSourceConfirmation}
- {dsListingDeleteDataSourceWarning}
+ {DS_LISTING_DATA_SOURCE_DELETE_ACTION}
+ {DS_LISTING_DATA_SOURCE_DELETE_IMPACT}
+ {DS_LISTING_DATA_SOURCE_DELETE_WARNING}
) : null;
};
@@ -214,9 +210,7 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
handleDisplayToastMessage({
id: 'dataSourcesManagement.dataSourceListing.deleteDataSourceFailMsg',
defaultMessage:
- 'Error occurred while deleting few/all selected records for Data sources. Please try it again',
- color: 'warning',
- iconType: 'alert',
+ 'Error occurred while deleting selected records for Data sources. Please try it again',
});
})
.finally(() => {
@@ -234,21 +228,13 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
};
/* Toast Handlers */
- const removeToast = (id: string) => {
- setToasts(toasts.filter((toast) => toast.id !== id));
- };
- const handleDisplayToastMessage = ({ id, defaultMessage, color, iconType }: ToastMessageItem) => {
- const failureMsg = ;
- setToasts([
- ...toasts,
- {
- title: failureMsg,
- id: failureMsg.props.id,
- color,
- iconType,
- },
- ]);
+ const handleDisplayToastMessage = ({ id, defaultMessage }: ToastMessageItem) => {
+ toasts.addWarning(
+ i18n.translate(id, {
+ defaultMessage,
+ })
+ );
};
/* Render Ui elements*/
@@ -261,11 +247,11 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
- {dsListingTitle}
+ {DS_LISTING_TITLE}
- {dsListingDescription}
+ {DS_LISTING_DESCRIPTION}
{createButton}
@@ -275,12 +261,46 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
/* Render table */
const renderTableContent = () => {
+ return (
+ <>
+ {/* Data sources table*/}
+
+ >
+ );
+ };
+
+ const renderEmptyState = () => {
+ return (
+ <>
+
+
+ {DS_LISTING_NO_DATA}
+
+ {createButton}
+
+
+ >
+ );
+ };
+
+ const renderContent = () => {
return (
<>
{/* Header */}
{renderHeader()}
@@ -290,46 +310,16 @@ export const DataSourceTable = ({ history }: RouteComponentProps) => {
{/* Delete confirmation modal*/}
{tableRenderDeleteModal()}
- {/* Data sources table*/}
-
+ {!isLoading && (!dataSources || !dataSources.length)
+ ? renderEmptyState()
+ : renderTableContent()}
{isDeleting ? : null}
>
);
};
- const renderContent = () => {
- return (
- <>
- {renderTableContent()}
- {}
- >
- );
- };
-
- return (
- <>
- {renderContent()}
- {
- removeToast(id);
- }}
- toastLifeTimeMs={toastLifeTimeMs}
- />
- >
- );
+ return renderContent();
};
export const DataSourceTableWithRouter = withRouter(DataSourceTable);
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 bf59853d5427..3edc2b1b7371 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
@@ -16,17 +16,15 @@ import {
EuiFormRow,
EuiHorizontalRule,
EuiPanel,
+ EuiRadioGroup,
EuiSpacer,
- EuiSuperSelect,
EuiText,
- EuiToolTip,
} from '@elastic/eui';
import {
AuthType,
credentialSourceOptions,
DataSourceAttributes,
DataSourceManagementContextValue,
- UpdatePasswordFormType,
UsernamePasswordTypedContent,
} from '../../../../types';
import { Header } from '../header';
@@ -34,40 +32,40 @@ import { context as contextType } from '../../../../../../opensearch_dashboards_
import {
CreateEditDataSourceValidation,
defaultValidation,
+ isTitleValid,
performDataSourceFormValidation,
} from '../../../validation';
import { UpdatePasswordModal } from '../update_password_modal';
import {
- authenticationDetailsDescription,
- authenticationDetailsText,
- authenticationMethodTitle,
- authenticationTitle,
- cancelChangesText,
- connectionDetailsText,
- createDataSourceDescriptionPlaceholder,
- createDataSourceEndpointURL,
- createDataSourcePasswordPlaceholder,
- createDataSourceUsernamePlaceholder,
- descriptionText,
- endpointDescription,
- endpointTitle,
- objectDetailsDescription,
- objectDetailsText,
- passwordText,
- saveChangesText,
- titleText,
- updatePasswordText,
- usernameText,
- validationErrorTooltipText,
+ AUTHENTICATION_METHOD,
+ AUTHENTICATION_TITLE,
+ CANCEL_CHANGES,
+ CONNECTION_DETAILS_TITLE,
+ DATA_SOURCE_DESCRIPTION_PLACEHOLDER,
+ DATA_SOURCE_PASSWORD_PLACEHOLDER,
+ USERNAME_PLACEHOLDER,
+ CREDENTIAL,
+ DESCRIPTION,
+ ENDPOINT_DESCRIPTION,
+ ENDPOINT_TITLE,
+ ENDPOINT_URL,
+ OBJECT_DETAILS_DESCRIPTION,
+ OBJECT_DETAILS_TITLE,
+ OPTIONAL,
+ PASSWORD,
+ SAVE_CHANGES,
+ TITLE,
+ UPDATE_STORED_PASSWORD,
+ USERNAME,
} from '../../../text_content';
export interface EditDataSourceProps {
existingDataSource: DataSourceAttributes;
+ existingDatasourceNamesList: string[];
handleSubmit: (formValues: DataSourceAttributes) => void;
onDeleteDataSource?: () => void;
}
export interface EditDataSourceState {
- formErrors: string[];
formErrorsByField: CreateEditDataSourceValidation;
title: string;
description: string;
@@ -78,9 +76,6 @@ export interface EditDataSourceState {
};
showUpdatePasswordModal: boolean;
showUpdateOptions: boolean;
- oldPassword: string;
- newPassword: string;
- confirmNewPassword: string;
}
export class EditDataSourceForm extends React.Component {
@@ -92,7 +87,6 @@ export class EditDataSourceForm extends React.Component {
this.setFormValuesForEditMode();
- this.setState({ showUpdateOptions: false }, this.checkValidation);
+ this.setState({ showUpdateOptions: false });
};
setFormValuesForEditMode() {
@@ -143,30 +134,49 @@ export class EditDataSourceForm extends React.Component {
- const { formErrors, formErrorsByField } = performDataSourceFormValidation(this.state);
-
- this.setState({
- formErrors,
- formErrorsByField,
- });
-
- return formErrors.length === 0;
+ return performDataSourceFormValidation(
+ this.state,
+ this.props.existingDatasourceNamesList,
+ this.props.existingDataSource.title
+ );
};
/* Events */
onChangeTitle = (e: { target: { value: any } }) => {
- this.setState({ title: e.target.value }, () => {
- if (this.state.formErrorsByField.title.length) {
- this.isFormValid();
- }
+ this.setState({ title: e.target.value });
+ };
+
+ validateTitle = () => {
+ const isValid = isTitleValid(
+ this.state.title,
+ this.props.existingDatasourceNamesList,
+ this.props.existingDataSource.title
+ );
+ this.setState({
+ formErrorsByField: {
+ ...this.state.formErrorsByField,
+ title: isValid.valid ? [] : [isValid.error],
+ },
});
};
- onChangeAuthType = (value: AuthType) => {
- this.setState({ auth: { ...this.state.auth, type: value } }, () => {
+ onChangeAuthType = (value: string) => {
+ const valueToSave =
+ value === AuthType.UsernamePasswordType ? AuthType.UsernamePasswordType : AuthType.NoAuth;
+
+ const formErrorsByField = {
+ ...this.state.formErrorsByField,
+ createCredential: { ...this.state.formErrorsByField.createCredential },
+ };
+ if (valueToSave === AuthType.NoAuth) {
+ formErrorsByField.createCredential = {
+ username: [],
+ password: [],
+ };
+ }
+ this.setState({ auth: { ...this.state.auth, type: valueToSave }, formErrorsByField }, () => {
this.onChangeFormValues();
- this.checkValidation();
});
};
@@ -175,33 +185,46 @@ export class EditDataSourceForm extends React.Component {
- this.setState(
- {
- auth: {
- ...this.state.auth,
- credentials: { ...this.state.auth.credentials, username: e.target.value },
+ this.setState({
+ auth: {
+ ...this.state.auth,
+ credentials: { ...this.state.auth.credentials, username: e.target.value },
+ },
+ });
+ };
+ validateUsername = () => {
+ const isValid = !!this.state.auth.credentials.username?.trim().length;
+ this.setState({
+ formErrorsByField: {
+ ...this.state.formErrorsByField,
+ createCredential: {
+ ...this.state.formErrorsByField.createCredential,
+ username: isValid ? [] : [''],
},
},
- this.checkValidation
- );
+ });
};
- onChangePassword = (e: { target: { value: any } }) => {
- this.setState(
- {
- auth: {
- ...this.state.auth,
- credentials: { ...this.state.auth.credentials, password: e.target.value },
+ validatePassword = () => {
+ const isValid = !!this.state.auth.credentials.password;
+ this.setState({
+ formErrorsByField: {
+ ...this.state.formErrorsByField,
+ createCredential: {
+ ...this.state.formErrorsByField.createCredential,
+ password: isValid ? [] : [''],
},
},
- this.checkValidation
- );
+ });
};
- checkValidation = () => {
- if (this.state.formErrors.length) {
- this.isFormValid();
- }
+ onChangePassword = (e: { target: { value: any } }) => {
+ this.setState({
+ auth: {
+ ...this.state.auth,
+ credentials: { ...this.state.auth.credentials, password: e.target.value },
+ },
+ });
};
onClickUpdateDataSource = () => {
@@ -242,8 +265,22 @@ export class EditDataSourceForm extends React.Component {
- // TODO: update password when API is ready
+ /* Update password */
+ updatePassword = (password: string) => {
+ const { title, description, auth } = this.props.existingDataSource;
+ const updateAttributes: DataSourceAttributes = {
+ title,
+ description,
+ endpoint: undefined,
+ auth: {
+ type: auth.type,
+ credentials: {
+ username: auth.credentials ? auth.credentials.username : '',
+ password,
+ },
+ },
+ };
+ this.props.handleSubmit(updateAttributes);
this.closePasswordModal();
};
@@ -257,10 +294,11 @@ export class EditDataSourceForm extends React.Component {
return (
<>
- {updatePasswordText}
+ {UPDATE_STORED_PASSWORD}
{this.state.showUpdatePasswordModal ? (
@@ -279,38 +317,50 @@ export class EditDataSourceForm extends React.Component {
+ return (
+ <>
+ {label} - {OPTIONAL}
+ >
+ );
+ };
+
/* Render Connection Details Panel */
renderConnectionDetailsSection = () => {
return (
- {connectionDetailsText}
+
+ {CONNECTION_DETAILS_TITLE}
+
{objectDetailsText}}
- description={{objectDetailsDescription} }
+ title={{OBJECT_DETAILS_TITLE}}
+ description={{OBJECT_DETAILS_DESCRIPTION} }
>
{/* Title */}
{/* Description */}
-
+
@@ -323,16 +373,18 @@ export class EditDataSourceForm extends React.Component {
return (
- {endpointTitle}
+
+ {ENDPOINT_TITLE}
+
{createDataSourceEndpointURL}}
- description={{endpointDescription} }
+ title={{ENDPOINT_URL}}
+ description={{ENDPOINT_DESCRIPTION} }
>
{/* Endpoint */}
-
+
{
return (
- {authenticationTitle}
+
+ {AUTHENTICATION_TITLE}
+
- {authenticationDetailsText}}
- description={{authenticationDetailsDescription} }
- >
+ {AUTHENTICATION_METHOD}}>
{this.renderCredentialsSection()}
@@ -368,11 +419,12 @@ export class EditDataSourceForm extends React.Component
{/* Auth type select */}
-
-
+ this.onChangeAuthType(value)}
+ idSelected={this.state.auth.type}
+ onChange={(id) => this.onChangeAuthType(id)}
+ name="Credential"
/>
@@ -387,47 +439,48 @@ export class EditDataSourceForm extends React.Component
{/* Username */}
-
+
{/* Password */}
- {this.props.existingDataSource.auth.type === AuthType.NoAuth
- ? this.renderEmptyPasswordField()
- : this.renderDisabledPasswordField()}
+
+
+
+
+
+ {this.props.existingDataSource.auth.type !== AuthType.NoAuth ? (
+ {this.renderUpdatePasswordModal()}
+ ) : null}
+
+
>
);
};
- renderDisabledPasswordField = () => {
- return (
-
-
-
- *************
-
- {this.renderUpdatePasswordModal()}
-
-
- );
- };
-
- renderEmptyPasswordField = () => {
- return (
-
-
-
- );
- };
-
didFormValuesChange = () => {
const formValues: DataSourceAttributes = {
title: this.state.title,
@@ -478,24 +531,22 @@ export class EditDataSourceForm extends React.Component
- {cancelChangesText}
+ {CANCEL_CHANGES}
-
-
- {saveChangesText}
-
-
+
+ {SAVE_CHANGES}
+
@@ -508,12 +559,7 @@ export class EditDataSourceForm extends React.Component
{this.renderHeader()}
- this.onChangeFormValues()}
- data-test-subj="data-source-edit"
- isInvalid={!!this.state.formErrors.length}
- error={this.state.formErrors}
- >
+ this.onChangeFormValues()} data-test-subj="data-source-edit">
{this.renderConnectionDetailsSection()}
diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx
index f9d957f090f0..974e29ff0635 100644
--- a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx
+++ b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx
@@ -18,13 +18,12 @@ import {
import { useOpenSearchDashboards } from '../../../../../../opensearch_dashboards_react/public';
import { DataSourceManagementContext } from '../../../../types';
import {
- cancelText,
- deleteText,
- deleteThisDataSource,
- dsListingDeleteDataSourceConfirmation,
- dsListingDeleteDataSourceDescription,
- dsListingDeleteDataSourceTitle,
- dsListingDeleteDataSourceWarning,
+ CANCEL_TEXT,
+ DELETE_TEXT,
+ DS_LISTING_DATA_SOURCE_DELETE_IMPACT,
+ DS_LISTING_DATA_SOURCE_DELETE_WARNING,
+ DELETE_THIS_DATA_SOURCE,
+ DS_UPDATE_DATA_SOURCE_DELETE_TITLE,
} from '../../../text_content';
export const Header = ({
@@ -47,7 +46,7 @@ export const Header = ({
const renderDeleteButton = () => {
return (
<>
-
+
{
@@ -56,13 +55,13 @@ export const Header = ({
iconType="trash"
iconSize="m"
size="m"
- aria-label={deleteThisDataSource}
+ aria-label={DELETE_THIS_DATA_SOURCE}
/>
{isDeleteModalVisible ? (
{
setIsDeleteModalVisible(false);
}}
@@ -70,13 +69,12 @@ export const Header = ({
setIsDeleteModalVisible(false);
onClickDeleteIcon();
}}
- cancelButtonText={cancelText}
- confirmButtonText={deleteText}
+ cancelButtonText={CANCEL_TEXT}
+ confirmButtonText={DELETE_TEXT}
defaultFocusedButton="confirm"
>
- {dsListingDeleteDataSourceDescription}
- {dsListingDeleteDataSourceConfirmation}
- {dsListingDeleteDataSourceWarning}
+ {DS_LISTING_DATA_SOURCE_DELETE_IMPACT}
+ {DS_LISTING_DATA_SOURCE_DELETE_WARNING}
) : null}
>
diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx
index df85ce1fd9af..a77e67f148ad 100644
--- a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx
+++ b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useState } from 'react';
import {
EuiButton,
EuiButtonEmpty,
@@ -15,125 +15,66 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
+ EuiText,
} from '@elastic/eui';
-import { UpdatePasswordFormType } from '../../../../types';
-import {
- defaultPasswordValidationByField,
- UpdatePasswordValidation,
- validateUpdatePassword,
-} from '../../../validation';
-import { confirmNewPasswordText, newPasswordText, oldPasswordText } from '../../../text_content';
+import { NEW_PASSWORD_TEXT, UPDATE_STORED_PASSWORD, USERNAME } from '../../../text_content';
export interface UpdatePasswordModalProps {
- handleUpdatePassword: (passwords: UpdatePasswordFormType) => void;
+ username: string;
+ handleUpdatePassword: (password: string) => void;
closeUpdatePasswordModal: () => void;
}
export const UpdatePasswordModal = ({
+ username,
handleUpdatePassword,
closeUpdatePasswordModal,
}: UpdatePasswordModalProps) => {
/* State Variables */
- const [formErrors, setFormErrors] = useState([]);
- const [formErrorsByField, setFormErrorsByField] = useState(
- defaultPasswordValidationByField
- );
- const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
- const [confirmNewPassword, setConfirmNewPassword] = useState('');
-
- const getFormValues = useCallback(() => {
- return {
- oldPassword,
- newPassword,
- confirmNewPassword,
- };
- }, [oldPassword, newPassword, confirmNewPassword]);
const onClickUpdatePassword = () => {
- if (isFormValid()) {
- handleUpdatePassword(getFormValues());
+ if (!!newPassword) {
+ handleUpdatePassword(newPassword);
}
};
- /* Validations */
- const isFormValid = useCallback(() => {
- const { formValidationErrors, formValidationErrorsByField } = validateUpdatePassword(
- getFormValues()
- );
-
- setFormErrors([...formValidationErrors]);
- setFormErrorsByField({ ...formValidationErrorsByField });
-
- return formValidationErrors.length === 0;
- }, [getFormValues]);
-
- useEffect(() => {
- if (formErrors.length) {
- isFormValid();
- }
- }, [oldPassword, newPassword, confirmNewPassword, formErrors.length, isFormValid]);
-
const renderUpdatePasswordModal = () => {
return (
- Update password
+ {UPDATE_STORED_PASSWORD}
-
-
- setOldPassword(e.target.value)}
- />
+
+ {/* Username */}
+
+ {username}
-
+ {/* Password */}
+
setNewPassword(e.target.value)}
/>
-
- setConfirmNewPassword(e.target.value)}
- />
-
Cancel
-
+
Update
diff --git a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx
index 0d27d650c273..e502adc5d5ca 100644
--- a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx
+++ b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx
@@ -6,16 +6,21 @@
import { RouteComponentProps, withRouter } from 'react-router-dom';
import React, { useState } from 'react';
import { useEffectOnce } from 'react-use';
-import { EuiGlobalToastList, EuiGlobalToastListToast } from '@elastic/eui';
-import { FormattedMessage } from '@osd/i18n/react';
+import { EuiSpacer } from '@elastic/eui';
+import { i18n } from '@osd/i18n';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
-import { DataSourceManagementContext, ToastMessageItem } from '../../types';
-import { deleteDataSourceById, getDataSourceById, updateDataSourceById } from '../utils';
+import { DataSourceManagementContext, DataSourceTableItem, ToastMessageItem } from '../../types';
+import {
+ deleteDataSourceById,
+ getDataSourceById,
+ getDataSources,
+ updateDataSourceById,
+} from '../utils';
import { getEditBreadcrumbs } from '../breadcrumbs';
import { EditDataSourceForm } from './components/edit_form/edit_data_source_form';
import { LoadingMask } from '../loading_mask';
import { AuthType, DataSourceAttributes } from '../../types';
-import { dataSourceNotFound } from '../text_content';
+import { DATA_SOURCE_NOT_FOUND } from '../text_content';
const defaultDataSource: DataSourceAttributes = {
title: '',
@@ -31,17 +36,17 @@ const EditDataSource: React.FunctionComponent
) => {
/* Initialization */
- const { savedObjects, setBreadcrumbs } = useOpenSearchDashboards<
- DataSourceManagementContext
- >().services;
+ const {
+ savedObjects,
+ setBreadcrumbs,
+ notifications: { toasts },
+ } = useOpenSearchDashboards().services;
const dataSourceID: string = props.match.params.id;
/* State Variables */
const [dataSource, setDataSource] = useState(defaultDataSource);
+ const [existingDatasourceNamesList, setExistingDatasourceNamesList] = useState([]);
const [isLoading, setIsLoading] = useState(false);
- const [toasts, setToasts] = useState([]);
-
- const toastLifeTimeMs: number = 6000;
/* Fetch data source by id*/
useEffectOnce(() => {
@@ -49,18 +54,21 @@ const EditDataSource: React.FunctionComponent datasource.title?.toLowerCase())
+ );
+ }
} catch (e) {
handleDisplayToastMessage({
id: 'dataSourcesManagement.editDataSource.editDataSourceFailMsg',
- defaultMessage: 'Unable to find the Data Source. Please try it again.',
- color: 'warning',
- iconType: 'alert',
+ defaultMessage: 'Unable to find the Data Source.',
});
-
props.history.push('');
} finally {
setIsLoading(false);
@@ -79,25 +87,12 @@ const EditDataSource: React.FunctionComponent {
- if (id && defaultMessage && color && iconType) {
- const failureMsg = ;
- setToasts([
- ...toasts,
- {
- title: failureMsg,
- id: failureMsg.props.id,
- color,
- iconType,
- },
- ]);
- }
+ const handleDisplayToastMessage = ({ id, defaultMessage }: ToastMessageItem) => {
+ toasts.addWarning(i18n.translate(id, { defaultMessage }));
};
/* Handle delete - data source*/
@@ -111,8 +106,6 @@ const EditDataSource: React.FunctionComponent
@@ -136,25 +130,14 @@ const EditDataSource: React.FunctionComponent {
- setToasts(toasts.filter((toast) => toast.id !== id));
- };
-
if (!isLoading && !dataSource?.endpoint) {
- return {dataSourceNotFound};
+ return {DATA_SOURCE_NOT_FOUND};
}
return (
<>
+
{renderContent()}
- {
- removeToast(id);
- }}
- toastLifeTimeMs={toastLifeTimeMs}
- />
>
);
};
diff --git a/src/plugins/data_source_management/public/components/text_content/text_content.ts b/src/plugins/data_source_management/public/components/text_content/text_content.ts
index ee6bca828eb0..56a2ea93be15 100644
--- a/src/plugins/data_source_management/public/components/text_content/text_content.ts
+++ b/src/plugins/data_source_management/public/components/text_content/text_content.ts
@@ -6,123 +6,141 @@
import { i18n } from '@osd/i18n';
/* Generic */
-export const cancelText = i18n.translate('cancel', {
+export const CANCEL_TEXT = i18n.translate('cancel', {
defaultMessage: 'Cancel',
});
-export const deleteText = i18n.translate('delete', {
+export const DELETE_TEXT = i18n.translate('delete', {
defaultMessage: 'Delete',
});
-export const titleText = i18n.translate('title', {
+export const TITLE = i18n.translate('title', {
defaultMessage: 'Title',
});
-export const descriptionText = i18n.translate('description', {
+export const DESCRIPTION = i18n.translate('description', {
defaultMessage: 'Description',
});
-export const usernameText = i18n.translate('username', {
+export const OPTIONAL = i18n.translate('optional', {
+ defaultMessage: 'optional',
+});
+
+export const USERNAME = i18n.translate('username', {
defaultMessage: 'Username',
});
-export const passwordText = i18n.translate('password', {
+export const PASSWORD = i18n.translate('password', {
defaultMessage: 'Password',
});
/* Datasource listing page */
-export const dsListingAriaRegion = i18n.translate(
+export const DS_LISTING_ARIA_REGION = i18n.translate(
'dataSourcesManagement.createDataSourcesLiveRegionAriaLabel',
{
defaultMessage: 'Data Sources',
}
);
-export const dsListingTitle = i18n.translate('dataSourcesManagement.dataSourcesTable.title', {
+export const DS_LISTING_TITLE = i18n.translate('dataSourcesManagement.dataSourcesTable.title', {
defaultMessage: 'Data Sources',
});
-export const dsListingDescription = i18n.translate(
+export const DS_LISTING_DESCRIPTION = i18n.translate(
'dataSourcesManagement.dataSourcesTable.description',
{
defaultMessage:
- 'Create and manage the data sources that help you retrieve your data from multiple Elasticsearch clusters',
+ 'Create and manage data source connections to help you retrieve data from multiple OpenSearch compatible sources.',
}
);
-export const dsListingPageTitle = i18n.translate(
+export const DS_LISTING_PAGE_TITLE = i18n.translate(
'dataSourcesManagement.dataSourcesTable.dataSourcesTitle',
{
defaultMessage: 'Data Sources',
}
);
-export const dsListingDeleteDataSourceTitle = i18n.translate(
- 'dataSourcesManagement.dataSourcesTable.deleteTitle',
+export const DS_LISTING_NO_DATA = i18n.translate('dataSourcesManagement.dataSourcesTable.noData', {
+ defaultMessage: 'No Data Source Connections have been created yet.',
+});
+
+export const DS_LISTING_DATA_SOURCE_MULTI_DELETE_TITLE = i18n.translate(
+ 'dataSourcesManagement.dataSourcesTable.multiDeleteTitle',
+ {
+ defaultMessage: 'Delete data source connection(s)',
+ }
+);
+
+export const DS_UPDATE_DATA_SOURCE_DELETE_TITLE = i18n.translate(
+ 'dataSourcesManagement.dataSourcesUpdate.deleteTitle',
{
- defaultMessage: 'Delete Data Source connection(s) permanently?',
+ defaultMessage: 'Delete data source connection',
}
);
-export const dsListingDeleteDataSourceDescription = i18n.translate(
+export const DS_LISTING_DATA_SOURCE_DELETE_ACTION = i18n.translate(
'dataSourcesManagement.dataSourcesTable.deleteDescription',
{
- defaultMessage:
- 'This will delete data source connections(s) and all Index Patterns using this credential will be invalid for access.',
+ defaultMessage: 'This action will delete the selected data source connections',
}
);
-export const dsListingDeleteDataSourceConfirmation = i18n.translate(
+export const DS_LISTING_DATA_SOURCE_DELETE_IMPACT = i18n.translate(
'dataSourcesManagement.dataSourcesTable.deleteConfirmation',
{
- defaultMessage: 'To confirm deletion, click delete button.',
+ defaultMessage:
+ 'Any objects created using data from these sources, including Index Patterns, Visualizations, and Observability Panels, will be impacted.',
}
);
-export const dsListingDeleteDataSourceWarning = i18n.translate(
+export const DS_LISTING_DATA_SOURCE_DELETE_WARNING = i18n.translate(
'dataSourcesManagement.dataSourcesTable.deleteWarning',
{
- defaultMessage: 'Note: this action is irrevocable!',
+ defaultMessage: 'This action cannot be undone.',
}
);
/* CREATE DATA SOURCE */
-export const createDataSourceHeader = i18n.translate(
- 'dataSourcesManagement.createDataSourceHeader',
+export const CREATE_DATA_SOURCE_BUTTON_TEXT = i18n.translate(
+ 'dataSourcesManagement.dataSourceListing.createButton',
{
defaultMessage: 'Create data source connection',
}
);
-export const createDataSourceDescriptionPlaceholder = i18n.translate(
- 'dataSourcesManagement.createDataSource.descriptionPlaceholder',
+export const CREATE_DATA_SOURCE_HEADER = i18n.translate(
+ 'dataSourcesManagement.createDataSourceHeader',
{
- defaultMessage: 'Description of the data source',
+ defaultMessage: 'Create data source connection',
}
);
-export const createDataSourceEndpointURL = i18n.translate(
- 'dataSourcesManagement.createDataSource.endpointURL',
+export const DATA_SOURCE_DESCRIPTION_PLACEHOLDER = i18n.translate(
+ 'dataSourcesManagement.createDataSource.descriptionPlaceholder',
{
- defaultMessage: 'Endpoint URL',
+ defaultMessage: 'Description of the data source',
}
);
-export const createDataSourceEndpointPlaceholder = i18n.translate(
+export const ENDPOINT_URL = i18n.translate('dataSourcesManagement.createDataSource.endpointURL', {
+ defaultMessage: 'Endpoint URL',
+});
+export const ENDPOINT_PLACEHOLDER = i18n.translate(
'dataSourcesManagement.createDataSource.endpointPlaceholder',
{
defaultMessage: 'The connection URL',
}
);
-export const createDataSourceUsernamePlaceholder = i18n.translate(
+export const USERNAME_PLACEHOLDER = i18n.translate(
'dataSourcesManagement.createDataSource.usernamePlaceholder',
{
defaultMessage: 'Username to connect to data source',
}
);
-export const createDataSourcePasswordPlaceholder = i18n.translate(
+export const DATA_SOURCE_PASSWORD_PLACEHOLDER = i18n.translate(
'dataSourcesManagement.createDataSource.passwordPlaceholder',
{
defaultMessage: 'Password to connect to data source',
}
);
-export const createDataSourceCredentialSource = i18n.translate(
+export const CREDENTIAL_SOURCE = i18n.translate(
'dataSourcesManagement.createDataSource.credentialSource',
{
defaultMessage: 'Credential Source',
@@ -130,83 +148,68 @@ export const createDataSourceCredentialSource = i18n.translate(
);
/* Edit data source */
-export const dataSourceNotFound = i18n.translate(
+export const DATA_SOURCE_NOT_FOUND = i18n.translate(
'dataSourcesManagement.editDataSource.dataSourceNotFound',
{
defaultMessage: 'Data Source not found!',
}
);
-export const deleteThisDataSource = i18n.translate(
+export const DELETE_THIS_DATA_SOURCE = i18n.translate(
'dataSourcesManagement.editDataSource.deleteThisDataSource',
{
defaultMessage: 'Delete this Data Source',
}
);
-export const oldPasswordText = i18n.translate('dataSourcesManagement.editDataSource.oldPassword', {
- defaultMessage: 'Old password',
-});
-export const newPasswordText = i18n.translate('dataSourcesManagement.editDataSource.newPassword', {
- defaultMessage: 'New password',
-});
-export const confirmNewPasswordText = i18n.translate(
- 'dataSourcesManagement.editDataSource.confirmNewPassword',
+export const NEW_PASSWORD_TEXT = i18n.translate(
+ 'dataSourcesManagement.editDataSource.newPassword',
{
- defaultMessage: 'Confirm new password',
+ defaultMessage: 'New password',
}
);
-export const updatePasswordText = i18n.translate(
- 'dataSourcesManagement.editDataSource.updatePasswordText',
+export const UPDATE_STORED_PASSWORD = i18n.translate(
+ 'dataSourcesManagement.editDataSource.updateStoredPassword',
{
- defaultMessage: 'Update password',
+ defaultMessage: 'Update stored password',
}
);
-export const connectionDetailsText = i18n.translate(
+export const CONNECTION_DETAILS_TITLE = i18n.translate(
'dataSourcesManagement.editDataSource.connectionDetailsText',
{
defaultMessage: 'Connection Details',
}
);
-export const objectDetailsText = i18n.translate(
+export const OBJECT_DETAILS_TITLE = i18n.translate(
'dataSourcesManagement.editDataSource.objectDetailsText',
{
defaultMessage: 'Object Details',
}
);
-export const objectDetailsDescription = i18n.translate(
+export const OBJECT_DETAILS_DESCRIPTION = i18n.translate(
'dataSourcesManagement.editDataSource.objectDetailsDescription',
{
defaultMessage:
'This connection information is used for reference in tables and when adding to a data source connection',
}
);
-export const authenticationMethodTitle = i18n.translate(
- 'dataSourcesManagement.editDataSource.authenticationMethodTitle',
- {
- defaultMessage: 'Authentication Method',
- }
-);
-export const authenticationTitle = i18n.translate(
+export const CREDENTIAL = i18n.translate('dataSourcesManagement.editDataSource.credential', {
+ defaultMessage: 'Credential',
+});
+export const AUTHENTICATION_TITLE = i18n.translate(
'dataSourcesManagement.editDataSource.authenticationTitle',
{
defaultMessage: 'Authentication',
}
);
-export const authenticationDetailsText = i18n.translate(
- 'dataSourcesManagement.editDataSource.authenticationDetailsText',
+export const AUTHENTICATION_METHOD = i18n.translate(
+ 'dataSourcesManagement.editDataSource.authenticationMethod',
{
- defaultMessage: 'Authentication Details',
- }
-);
-export const authenticationDetailsDescription = i18n.translate(
- 'dataSourcesManagement.editDataSource.authenticationDetailsDescription',
- {
- defaultMessage: 'Modify these to update the authentication type and associated details',
+ defaultMessage: 'Authentication Method',
}
);
-export const endpointTitle = i18n.translate('dataSourcesManagement.editDataSource.endpointTitle', {
+export const ENDPOINT_TITLE = i18n.translate('dataSourcesManagement.editDataSource.endpointTitle', {
defaultMessage: 'Endpoint',
});
-export const endpointDescription = i18n.translate(
+export const ENDPOINT_DESCRIPTION = i18n.translate(
'dataSourcesManagement.editDataSource.endpointDescription',
{
defaultMessage:
@@ -214,70 +217,20 @@ export const endpointDescription = i18n.translate(
}
);
-export const cancelChangesText = i18n.translate(
+export const CANCEL_CHANGES = i18n.translate(
'dataSourcesManagement.editDataSource.cancelButtonLabel',
{
defaultMessage: 'Cancel changes',
}
);
-export const saveChangesText = i18n.translate(
- 'dataSourcesManagement.editDataSource.saveButtonLabel',
- {
- defaultMessage: 'Save changes',
- }
-);
-
-export const validationErrorTooltipText = i18n.translate(
- 'dataSourcesManagement.editDataSource.saveButtonTooltipWithInvalidChanges',
- {
- defaultMessage: 'Fix invalid settings before saving.',
- }
-);
-
-/* Password validation */
-
-export const dataSourceValidationOldPasswordEmpty = i18n.translate(
- 'dataSourcesManagement.validation.oldPasswordEmpty',
- {
- defaultMessage: 'Old password cannot be empty',
- }
-);
-export const dataSourceValidationNewPasswordEmpty = i18n.translate(
- 'dataSourcesManagement.validation.newPasswordEmpty',
- {
- defaultMessage: 'New password cannot be empty',
- }
-);
-export const dataSourceValidationNoPasswordMatch = i18n.translate(
- 'dataSourcesManagement.validation.noPasswordMatch',
- {
- defaultMessage: 'Passwords do not match',
- }
-);
+export const SAVE_CHANGES = i18n.translate('dataSourcesManagement.editDataSource.saveButtonLabel', {
+ defaultMessage: 'Save changes',
+});
/* Create/Edit validation */
-
-export const dataSourceValidationTitleEmpty = i18n.translate(
- 'dataSourcesManagement.validation.titleEmpty',
- {
- defaultMessage: 'Title must not be empty',
- }
-);
-export const dataSourceValidationEndpointNotValid = i18n.translate(
- 'dataSourcesManagement.validation.endpointNotValid',
- {
- defaultMessage: 'Endpoint is not valid',
- }
-);
-export const dataSourceValidationUsernameEmpty = i18n.translate(
- 'dataSourcesManagement.validation.usernameEmpty',
- {
- defaultMessage: 'Username should not be empty',
- }
-);
-export const dataSourceValidationPasswordEmpty = i18n.translate(
- 'dataSourcesManagement.validation.passwordEmpty',
+export const DATA_SOURCE_VALIDATION_TITLE_EXISTS = i18n.translate(
+ 'dataSourcesManagement.validation.titleExists',
{
- defaultMessage: 'Password should not be empty',
+ defaultMessage: 'This title is already in use',
}
);
diff --git a/src/plugins/data_source_management/public/components/validation/datasource_form_validation.test.ts b/src/plugins/data_source_management/public/components/validation/datasource_form_validation.test.ts
index 0c64abc04878..9a00d0f29c91 100644
--- a/src/plugins/data_source_management/public/components/validation/datasource_form_validation.test.ts
+++ b/src/plugins/data_source_management/public/components/validation/datasource_form_validation.test.ts
@@ -6,17 +6,12 @@
import { AuthType } from '../../types';
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 {
- defaultValidation,
- performDataSourceFormValidation,
- validateUpdatePassword,
-} from './datasource_form_validation';
+import { defaultValidation, performDataSourceFormValidation } from './datasource_form_validation';
import { mockDataSourceAttributesWithAuth } from '../../mocks';
describe('DataSourceManagement: Form Validation', () => {
describe('validate create/edit datasource', () => {
let form: CreateDataSourceState | EditDataSourceState = {
- formErrors: [],
formErrorsByField: { ...defaultValidation },
title: '',
description: '',
@@ -29,46 +24,46 @@ describe('DataSourceManagement: Form Validation', () => {
},
},
};
- test('should fail validation on all fields', () => {
- const result = performDataSourceFormValidation(form);
- expect(result.formErrors.length).toBe(4);
+ test('should fail validation when title is empty', () => {
+ const result = performDataSourceFormValidation(form, [], '');
+ expect(result).toBe(false);
+ });
+ test('should fail validation on duplicate title', () => {
+ form.title = 'test';
+ const result = performDataSourceFormValidation(form, ['oldTitle', 'test'], 'oldTitle');
+ expect(result).toBe(false);
+ });
+ test('should fail validation when endpoint is not valid', () => {
+ form.endpoint = mockDataSourceAttributesWithAuth.endpoint;
+ const result = performDataSourceFormValidation(form, [], '');
+ expect(result).toBe(false);
+ });
+ test('should fail validation when username is empty', () => {
+ form.endpoint = 'test';
+ const result = performDataSourceFormValidation(form, [], '');
+ 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, [], '');
+ expect(result).toBe(false);
});
test('should NOT fail validation on empty username/password when No Auth is selected', () => {
form.auth.type = AuthType.NoAuth;
- const result = performDataSourceFormValidation(form);
- expect(result.formErrors.length).toBe(2);
- expect(result.formErrorsByField.createCredential.username.length).toBe(0);
- expect(result.formErrorsByField.createCredential.password.length).toBe(0);
+ form.title = 'test';
+ form.endpoint = mockDataSourceAttributesWithAuth.endpoint;
+ const result = performDataSourceFormValidation(form, [], '');
+ expect(result).toBe(true);
});
test('should NOT fail validation on all fields', () => {
form = { ...form, ...mockDataSourceAttributesWithAuth };
- const result = performDataSourceFormValidation(form);
- expect(result.formErrors.length).toBe(0);
- });
- });
-
- describe('validate passwords', () => {
- const passwords = {
- oldPassword: '',
- newPassword: '',
- confirmNewPassword: '',
- };
- test('should fail validation for all fields', () => {
- const result = validateUpdatePassword(passwords);
- expect(result.formValidationErrors.length).toBe(2);
- });
- test('should fail validation when passwords do not match', () => {
- passwords.oldPassword = 'test';
- passwords.newPassword = 'test123';
- const result = validateUpdatePassword(passwords);
- expect(result.formValidationErrors.length).toBe(1);
- expect(result.formValidationErrorsByField.confirmNewPassword.length).toBe(1);
- });
- test('should NOT fail validation ', () => {
- passwords.confirmNewPassword = 'test123';
- const result = validateUpdatePassword(passwords);
- expect(result.formValidationErrors.length).toBe(0);
- expect(result.formValidationErrorsByField.confirmNewPassword.length).toBe(0);
+ const result = performDataSourceFormValidation(
+ form,
+ [mockDataSourceAttributesWithAuth.title],
+ mockDataSourceAttributesWithAuth.title
+ );
+ 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 9135f4292923..4e5a465d57fb 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,19 +4,10 @@
*/
import { isValidUrl } from '../utils';
+import { DATA_SOURCE_VALIDATION_TITLE_EXISTS } from '../text_content';
import { CreateDataSourceState } from '../create_data_source_wizard/components/create_form/create_data_source_form';
-import { AuthType } from '../../types';
import { EditDataSourceState } from '../edit_data_source/components/edit_form/edit_data_source_form';
-import { UpdatePasswordFormType } from '../../types';
-import {
- dataSourceValidationEndpointNotValid,
- dataSourceValidationNewPasswordEmpty,
- dataSourceValidationNoPasswordMatch,
- dataSourceValidationOldPasswordEmpty,
- dataSourceValidationPasswordEmpty,
- dataSourceValidationTitleEmpty,
- dataSourceValidationUsernameEmpty,
-} from '../text_content';
+import { AuthType } from '../../types';
export interface CreateEditDataSourceValidation {
title: string[];
@@ -27,12 +18,6 @@ export interface CreateEditDataSourceValidation {
};
}
-export interface UpdatePasswordValidation {
- oldPassword: string[];
- newPassword: string[];
- confirmNewPassword: string[];
-}
-
export const defaultValidation: CreateEditDataSourceValidation = {
title: [],
endpoint: [],
@@ -41,34 +26,46 @@ export const defaultValidation: CreateEditDataSourceValidation = {
password: [],
},
};
-export const defaultPasswordValidationByField: UpdatePasswordValidation = {
- oldPassword: [],
- newPassword: [],
- confirmNewPassword: [],
+
+export const isTitleValid = (
+ title: string,
+ existingDatasourceNamesList: string[],
+ existingTitle: string
+) => {
+ const isValid = {
+ valid: true,
+ error: '',
+ };
+ /* Title validation */
+ if (!title?.trim?.().length) {
+ isValid.valid = false;
+ } else if (
+ title.toLowerCase() !== existingTitle.toLowerCase() &&
+ Array.isArray(existingDatasourceNamesList) &&
+ existingDatasourceNamesList.includes(title.toLowerCase())
+ ) {
+ /* title already exists */
+ isValid.valid = false;
+ isValid.error = DATA_SOURCE_VALIDATION_TITLE_EXISTS;
+ }
+ return isValid;
};
export const performDataSourceFormValidation = (
- formValues: CreateDataSourceState | EditDataSourceState
+ formValues: CreateDataSourceState | EditDataSourceState,
+ existingDatasourceNamesList: string[],
+ existingTitle: string
) => {
- const validationByField: CreateEditDataSourceValidation = {
- title: [],
- endpoint: [],
- createCredential: {
- username: [],
- password: [],
- },
- };
- const formErrorMessages: string[] = [];
/* Title validation */
- if (!formValues?.title?.trim?.().length) {
- validationByField.title.push(dataSourceValidationTitleEmpty);
- formErrorMessages.push(dataSourceValidationTitleEmpty);
+ const titleValid = isTitleValid(formValues?.title, existingDatasourceNamesList, existingTitle);
+
+ if (!titleValid.valid) {
+ return false;
}
/* Endpoint Validation */
if (!isValidUrl(formValues?.endpoint)) {
- validationByField.endpoint.push(dataSourceValidationEndpointNotValid);
- formErrorMessages.push(dataSourceValidationEndpointNotValid);
+ return false;
}
/* Credential Validation */
@@ -77,46 +74,14 @@ export const performDataSourceFormValidation = (
if (formValues?.auth?.type === AuthType.UsernamePasswordType) {
/* Username */
if (!formValues.auth.credentials?.username) {
- validationByField.createCredential.username.push(dataSourceValidationUsernameEmpty);
- formErrorMessages.push(dataSourceValidationUsernameEmpty);
+ return false;
}
/* password */
if (!formValues.auth.credentials?.password) {
- validationByField.createCredential.password.push(dataSourceValidationPasswordEmpty);
- formErrorMessages.push(dataSourceValidationPasswordEmpty);
+ return false;
}
}
- return {
- formErrors: formErrorMessages,
- formErrorsByField: { ...validationByField },
- };
-};
-
-export const validateUpdatePassword = (passwords: UpdatePasswordFormType) => {
- const validationByField: UpdatePasswordValidation = {
- oldPassword: [],
- newPassword: [],
- confirmNewPassword: [],
- };
-
- const formErrorMessages: string[] = [];
-
- if (!passwords.oldPassword) {
- validationByField.oldPassword.push(dataSourceValidationOldPasswordEmpty);
- formErrorMessages.push(dataSourceValidationOldPasswordEmpty);
- }
- if (!passwords.newPassword) {
- validationByField.newPassword.push(dataSourceValidationNewPasswordEmpty);
- formErrorMessages.push(dataSourceValidationNewPasswordEmpty);
- } else if (passwords.confirmNewPassword !== passwords.newPassword) {
- validationByField.confirmNewPassword.push(dataSourceValidationNoPasswordMatch);
- formErrorMessages.push(dataSourceValidationNoPasswordMatch);
- }
-
- return {
- formValidationErrors: formErrorMessages,
- formValidationErrorsByField: { ...validationByField },
- };
+ return true;
};
diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts
index 31ab8237a443..159295a5a808 100644
--- a/src/plugins/data_source_management/public/plugin.ts
+++ b/src/plugins/data_source_management/public/plugin.ts
@@ -27,6 +27,7 @@ export class DataSourceManagementPlugin
opensearchDashboardsSection.registerApp({
id: DSM_APP_ID,
title: PLUGIN_NAME,
+ showExperimentalBadge: true,
order: 1,
mount: async (params) => {
const { mountManagementSection } = await import('./management_app');
diff --git a/src/plugins/data_source_management/public/types.ts b/src/plugins/data_source_management/public/types.ts
index c0aa502b5830..a689cb4a593e 100644
--- a/src/plugins/data_source_management/public/types.ts
+++ b/src/plugins/data_source_management/public/types.ts
@@ -42,20 +42,12 @@ export interface DataSourceTableItem {
export interface ToastMessageItem {
id: string;
defaultMessage: string;
- color: 'primary' | 'success' | 'warning' | 'danger';
- iconType: string;
}
export type DataSourceManagementContextValue = OpenSearchDashboardsReactContextValue<
DataSourceManagementContext
>;
-export interface UpdatePasswordFormType {
- oldPassword: string;
- newPassword: string;
- confirmNewPassword: string;
-}
-
/* Datasource types */
export enum AuthType {
NoAuth = 'no_auth',
@@ -63,8 +55,8 @@ export enum AuthType {
}
export const credentialSourceOptions = [
- { value: AuthType.UsernamePasswordType, inputDisplay: 'Username & Password' },
- { value: AuthType.NoAuth, inputDisplay: 'No authentication' },
+ { id: AuthType.NoAuth, label: 'No authentication' },
+ { id: AuthType.UsernamePasswordType, label: 'Username & Password' },
];
export interface DataSourceAttributes extends SavedObjectAttributes {
diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss
index 7b134f141d78..f9e5a74d4f35 100644
--- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss
+++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss
@@ -9,3 +9,7 @@
margin-bottom: $euiSize;
}
}
+
+.mgtSideBarNavItemExperimentalBadge {
+ margin-left: 10px;
+}
diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx
index bb99481fb2b5..6cb6b6aeb164 100644
--- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx
+++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx
@@ -28,7 +28,7 @@
* under the License.
*/
-import React, { useState } from 'react';
+import React, { ReactNode, useState } from 'react';
import { i18n } from '@osd/i18n';
import { sortBy } from 'lodash';
@@ -40,8 +40,10 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiToolTip,
+ EuiBadge,
} from '@elastic/eui';
import { AppMountParameters } from 'opensearch-dashboards/public';
+import { FormattedMessage } from '@osd/i18n/react';
import { ManagementApp, ManagementSection } from '../../utils';
import './management_sidebar_nav.scss';
@@ -99,7 +101,7 @@ export const ManagementSidebarNav = ({
}));
interface TooltipWrapperProps {
- text: string;
+ text: ReactNode | string;
tip?: string;
}
@@ -115,15 +117,28 @@ export const ManagementSidebarNav = ({
);
+ const TitleWithExperimentalBadge = ({ title }: any) => (
+ <>
+ {title}
+
+
+
+ >
+ );
+
const createNavItem = (
item: T,
customParams: Partial> = {}
) => {
const iconType = item.euiIconType || item.icon;
-
+ const name = item.showExperimentalBadge ? (
+
+ ) : (
+ item.title
+ );
return {
id: item.id,
- name: item.tip ? : item.title,
+ name: item.tip ? : name,
isSelected: item.id === selectedId,
icon: iconType ? : undefined,
'data-test-subj': item.id,
diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts
index be2970427759..a10a0095589c 100644
--- a/src/plugins/management/public/types.ts
+++ b/src/plugins/management/public/types.ts
@@ -90,6 +90,7 @@ export interface CreateManagementItemArgs {
title: string;
tip?: string;
order?: number;
+ showExperimentalBadge?: boolean;
euiIconType?: EuiIconType; // takes precedence over `icon` property.
icon?: string; // URL to image file; fallback if no `euiIconType`
}
diff --git a/src/plugins/management/public/utils/management_item.ts b/src/plugins/management/public/utils/management_item.ts
index 9e90334f36da..de79c626f40f 100644
--- a/src/plugins/management/public/utils/management_item.ts
+++ b/src/plugins/management/public/utils/management_item.ts
@@ -36,18 +36,28 @@ export class ManagementItem {
public readonly title: string;
public readonly tip?: string;
public readonly order: number;
+ public readonly showExperimentalBadge: boolean;
public readonly euiIconType?: EuiIconType;
public readonly icon?: string;
public enabled: boolean = true;
- constructor({ id, title, tip, order = 100, euiIconType, icon }: CreateManagementItemArgs) {
+ constructor({
+ id,
+ title,
+ tip,
+ order = 100,
+ euiIconType,
+ icon,
+ showExperimentalBadge,
+ }: CreateManagementItemArgs) {
this.id = id;
this.title = title;
this.tip = tip;
this.order = order;
this.euiIconType = euiIconType;
this.icon = icon;
+ this.showExperimentalBadge = !!showExperimentalBadge;
}
disable() {
|