diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx b/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx index 6c7bf347da00..3bbf6ea5e1b5 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/create_data_source_wizard.tsx @@ -47,7 +47,7 @@ const CreateDataSourceWizard: React.FunctionComponent void; } export const CredentialsComboBox: React.FunctionComponent = ({ + isInvalid, availableCredentials, selectedCredentials, setSelectedCredentials, @@ -44,6 +46,7 @@ export const CredentialsComboBox: React.FunctionComponent onOptionsChanged(options)} isClearable={true} + isInvalid={isInvalid} /> ); }; diff --git a/src/plugins/data_source_management/public/components/create_edit_data_source_wizard/create_edit_data_source_wizard.tsx b/src/plugins/data_source_management/public/components/create_edit_data_source_wizard/create_edit_data_source_wizard.tsx index f01d00f417c5..d6ad3b398951 100644 --- a/src/plugins/data_source_management/public/components/create_edit_data_source_wizard/create_edit_data_source_wizard.tsx +++ b/src/plugins/data_source_management/public/components/create_edit_data_source_wizard/create_edit_data_source_wizard.tsx @@ -32,7 +32,7 @@ import { ToastMessageItem, } from '../../types'; import { Header } from './components/header'; -import { getExistingCredentials } from '../utils'; +import { getExistingCredentials, isValidUrl } from '../utils'; import { MODE_CREATE, MODE_EDIT } from '../../../common'; import { context as contextType } from '../../../../opensearch_dashboards_react/public'; @@ -59,12 +59,14 @@ interface CreateEditDataSourceValidation { title: string[]; description: string[]; endpoint: string[]; + credential: string[]; } const defaultValidation: CreateEditDataSourceValidation = { title: [], description: [], endpoint: [], + credential: [], }; export class CreateEditDataSourceWizard extends React.Component< @@ -147,20 +149,30 @@ export class CreateEditDataSourceWizard extends React.Component< title: [], description: [], endpoint: [], + credential: [], }; const formErrorMessages: string[] = []; + /* Title validation */ if (!this.state.dataSourceTitle) { validationByField.title.push('Title should not be empty'); formErrorMessages.push('Title should not be empty'); } + /* Description Validation */ if (!this.state.dataSourceDescription) { validationByField.description.push('Description should not be empty'); formErrorMessages.push('Description should not be empty'); } - if (!this.state.endpoint) { - validationByField.endpoint.push('Endpoint should not be empty'); - formErrorMessages.push('Endpoint should not be empty'); + /* Endpoint Validation */ + if (!isValidUrl(this.state.endpoint)) { + validationByField.endpoint.push('Endpoint is not valid'); + formErrorMessages.push('Endpoint is not valid'); } + /* Credential Validation */ + if (!this.state.noAuthentication && !this.state.selectedCredentials?.length) { + validationByField.credential.push('Please associate a credential'); + formErrorMessages.push('Please associate a credential'); + } + this.setState({ formErrors: formErrorMessages, formErrorsByField: { ...validationByField }, @@ -218,7 +230,11 @@ export class CreateEditDataSourceWizard extends React.Component< }; onSelectExistingCredentials = (options: CredentialsComboBoxItem[]) => { - this.setState({ selectedCredentials: options }); + this.setState({ selectedCredentials: options }, () => { + if (this.state.formErrorsByField.credential.length) { + this.isFormValid(); + } + }); }; onCreateStoredCredential = () => { @@ -279,8 +295,13 @@ export class CreateEditDataSourceWizard extends React.Component< <> - + { + try { + return Boolean(new URL(endpoint)); + } catch (e) { + return false; + } +}; diff --git a/src/plugins/home/server/tutorials/haproxy_metrics/index.ts b/src/plugins/home/server/tutorials/haproxy_metrics/index.ts new file mode 100644 index 000000000000..be275726877a --- /dev/null +++ b/src/plugins/home/server/tutorials/haproxy_metrics/index.ts @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@osd/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { onPremInstructions } from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function haproxyMetricsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'haproxy'; + return { + id: 'haproxyMetrics', + name: i18n.translate('home.tutorials.haproxyMetrics.nameTitle', { + defaultMessage: 'HAProxy metrics', + }), + moduleName, + isBeta: false, + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.haproxyMetrics.shortDescription', { + defaultMessage: 'Fetch internal metrics from the HAProxy server.', + }), + longDescription: i18n.translate('home.tutorials.haproxyMetrics.longDescription', { + defaultMessage: + 'The `haproxy` Metricbeat module fetches internal metrics from HAProxy. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-haproxy.html', + }, + }), + euiIconType: 'logoHAproxy', + artifacts: { + application: { + label: i18n.translate('home.tutorials.haproxyMetrics.artifacts.application.label', { + defaultMessage: 'Discover', + }), + path: '/app/discover#/', + }, + dashboards: [], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-haproxy.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, context), + }; +}