Skip to content

Commit

Permalink
[MD] noAuth integration, credential & endpoint validation (opensearch…
Browse files Browse the repository at this point in the history
…-project#2165)

* noAuth integration, credential & endpoint validation

Signed-off-by: mpabba3003 <amazonmanideep@gmail.com>

* Refactoring validation message

Signed-off-by: mpabba3003 <amazonmanideep@gmail.com>

* Adding back accidentally deleted file home/tutorials/haproxy_metrics/index.ts

Signed-off-by: mpabba3003 <amazonmanideep@gmail.com>

Signed-off-by: mpabba3003 <amazonmanideep@gmail.com>
Signed-off-by: Kristen Tian <tyarong@amazon.com>
  • Loading branch information
mpabba3003 authored and kristenTian committed Sep 15, 2022
1 parent 0ca5bb1 commit 5c1b3fd
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const CreateDataSourceWizard: React.FunctionComponent<CreateDataSourceWizardProp
// TODO: Add rendering spinner

const references = [];
const attributes = { title, description, endpoint };
const attributes = { title, description, endpoint, noAuth: noAuthentication };

if (credentialId) {
references.push({ id: credentialId, type: 'credential', name: 'credential' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import { CredentialsComboBoxItem } from '../../../../types';

interface CredentialsComboBoxProps {
isInvalid: boolean;
selectedCredentials: CredentialsComboBoxItem[];
availableCredentials: CredentialsComboBoxItem[];
setSelectedCredentials: (selectedOptions: CredentialsComboBoxItem[]) => void;
}

export const CredentialsComboBox: React.FunctionComponent<CredentialsComboBoxProps> = ({
isInvalid,
availableCredentials,
selectedCredentials,
setSelectedCredentials,
Expand Down Expand Up @@ -44,6 +46,7 @@ export const CredentialsComboBox: React.FunctionComponent<CredentialsComboBoxPro
selectedOptions={selectedCredentials}
onChange={(options: EuiComboBoxOptionOption[]) => onOptionsChanged(options)}
isClearable={true}
isInvalid={isInvalid}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -279,8 +295,13 @@ export class CreateEditDataSourceWizard extends React.Component<
<>
<EuiFlexGroup style={{ maxWidth: 600 }}>
<EuiFlexItem>
<EuiFormRow hasEmptyLabelSpace>
<EuiFormRow
hasEmptyLabelSpace
isInvalid={!!this.state.formErrorsByField.credential.length}
error={this.state.formErrorsByField.credential}
>
<CredentialsComboBox
isInvalid={!!this.state.formErrorsByField.credential.length}
availableCredentials={this.state.availableCredentials}
selectedCredentials={this.state.selectedCredentials}
setSelectedCredentials={this.onSelectExistingCredentials}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const EditDataSource: React.FunctionComponent<RouteComponentProps<{ id: string }
// TODO: Add rendering spanner https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2050

const references = [];
const attributes = { title, description, endpoint };
const attributes = { title, description, endpoint, noAuth: noAuthentication };

if (credentialId) {
references.push({ id: credentialId, type: 'credential', name: 'credential' });
Expand Down
14 changes: 11 additions & 3 deletions src/plugins/data_source_management/public/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ export async function getDataSourceById(
endpoint: attributes.endpoint,
description: attributes.description || '',
credentialId,
noAuthentication: false, // TODO: get noAuthentication from response
noAuthentication: !!attributes.noAuth,
};
}) || null
);
}

export async function createSingleDataSource(
savedObjectsClient: SavedObjectsClientContract,
attributes: { title: string; description: string; endpoint: string },
attributes: { title: string; description: string; endpoint: string; noAuth: boolean },
options?: { references: any[] }
) {
return savedObjectsClient.create('data-source', attributes, options);
Expand All @@ -76,7 +76,7 @@ export async function createSingleDataSource(
export async function updateDataSourceById(
savedObjectsClient: SavedObjectsClientContract,
id: string,
attributes: { title: string; description: string; endpoint: string },
attributes: { title: string; description: string; endpoint: string; noAuth: boolean },
options?: { references: any[] }
) {
return savedObjectsClient.update('data-source', id, attributes, options);
Expand Down Expand Up @@ -106,3 +106,11 @@ export async function getExistingCredentials(savedObjectsClient: SavedObjectsCli
}) || []
);
}

export const isValidUrl = (endpoint: string) => {
try {
return Boolean(new URL(endpoint));
} catch (e) {
return false;
}
};
76 changes: 76 additions & 0 deletions src/plugins/home/server/tutorials/haproxy_metrics/index.ts
Original file line number Diff line number Diff line change
@@ -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),
};
}

0 comments on commit 5c1b3fd

Please sign in to comment.