Skip to content

Commit

Permalink
[Ingest Manager] Better validation of settings form (#67297)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet committed May 27, 2020
1 parent dbbd090 commit 5ac3710
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,37 +27,71 @@ import { EuiText } from '@elastic/eui';
import { useInput, useComboInput, useCore, useGetSettings, sendPutSettings } from '../hooks';
import { useGetOutputs, sendPutOutput } from '../hooks/use_request/outputs';

const URL_REGEX = /^(https?):\/\/[^\s$.?#].[^\s]*$/gm;

interface Props {
onClose: () => void;
}

function useSettingsForm(outputId: string | undefined) {
function useSettingsForm(outputId: string | undefined, onSuccess: () => void) {
const [isLoading, setIsloading] = React.useState(false);
const { notifications } = useCore();
const kibanaUrlInput = useInput();
const elasticsearchUrlInput = useComboInput([]);
const kibanaUrlInput = useInput('', (value) => {
if (!value.match(URL_REGEX)) {
return [
i18n.translate('xpack.ingestManager.settings.kibanaUrlError', {
defaultMessage: 'Invalid URL',
}),
];
}
});
const elasticsearchUrlInput = useComboInput([], (value) => {
if (value.some((v) => !v.match(URL_REGEX))) {
return [
i18n.translate('xpack.ingestManager.settings.elasticHostError', {
defaultMessage: 'Invalid URL',
}),
];
}
});

return {
isLoading,
onSubmit: async () => {
if (!kibanaUrlInput.validate() || !elasticsearchUrlInput.validate()) {
return;
}

try {
setIsloading(true);
if (!outputId) {
throw new Error('Unable to load outputs');
}
await sendPutOutput(outputId, {
const outputResponse = await sendPutOutput(outputId, {
hosts: elasticsearchUrlInput.value,
});
await sendPutSettings({
if (outputResponse.error) {
throw outputResponse.error;
}
const settingsResponse = await sendPutSettings({
kibana_url: kibanaUrlInput.value,
});
if (settingsResponse.error) {
throw settingsResponse.error;
}
notifications.toasts.addSuccess(
i18n.translate('xpack.ingestManager.settings.success.message', {
defaultMessage: 'Settings saved',
})
);
setIsloading(false);
onSuccess();
} catch (error) {
setIsloading(false);
notifications.toasts.addError(error, {
title: 'Error',
});
}
notifications.toasts.addSuccess(
i18n.translate('xpack.ingestManager.settings.success.message', {
defaultMessage: 'Settings saved',
})
);
},
inputs: {
kibanaUrl: kibanaUrlInput,
Expand All @@ -72,7 +106,7 @@ export const SettingFlyout: React.FunctionComponent<Props> = ({ onClose }) => {
const settings = settingsRequest?.data?.item;
const outputsRequest = useGetOutputs();
const output = outputsRequest.data?.items?.[0];
const { inputs, onSubmit } = useSettingsForm(output?.id);
const { inputs, onSubmit, isLoading } = useSettingsForm(output?.id, onClose);

useEffect(() => {
if (output) {
Expand Down Expand Up @@ -185,6 +219,7 @@ export const SettingFlyout: React.FunctionComponent<Props> = ({ onClose }) => {
label={i18n.translate('xpack.ingestManager.settings.kibanaUrlLabel', {
defaultMessage: 'Kibana URL',
})}
{...inputs.kibanaUrl.formRowProps}
>
<EuiFieldText required={true} {...inputs.kibanaUrl.props} name="kibanaUrl" />
</EuiFormRow>
Expand All @@ -195,6 +230,7 @@ export const SettingFlyout: React.FunctionComponent<Props> = ({ onClose }) => {
label={i18n.translate('xpack.ingestManager.settings.elasticsearchUrlLabel', {
defaultMessage: 'Elasticsearch URL',
})}
{...inputs.elasticsearchUrl.formRowProps}
>
<EuiComboBox noSuggestions {...inputs.elasticsearchUrl.props} />
</EuiFormRow>
Expand Down Expand Up @@ -226,7 +262,7 @@ export const SettingFlyout: React.FunctionComponent<Props> = ({ onClose }) => {
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={onSubmit} iconType="save">
<EuiButton onClick={onSubmit} iconType="save" isLoading={isLoading}>
<FormattedMessage
id="xpack.ingestManager.settings.saveButtonLabel"
defaultMessage="Save settings"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,90 @@

import React from 'react';

export function useInput(defaultValue = '') {
export function useInput(defaultValue = '', validate?: (value: string) => string[] | undefined) {
const [value, setValue] = React.useState<string>(defaultValue);
const [errors, setErrors] = React.useState<string[] | undefined>();

const onChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const newValue = e.target.value;
setValue(newValue);
if (errors && validate && validate(newValue) === undefined) {
setErrors(undefined);
}
};

const isInvalid = errors !== undefined;

return {
value,
errors,
props: {
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
setValue(e.target.value);
},
onChange,
value,
isInvalid,
},
formRowProps: {
error: errors,
isInvalid,
},
clear: () => {
setValue('');
},
validate: () => {
if (validate) {
const newErrors = validate(value);
setErrors(newErrors);
return newErrors === undefined;
}

return true;
},
setValue,
};
}

export function useComboInput(defaultValue = []) {
export function useComboInput(
defaultValue = [],
validate?: (value: string[]) => string[] | undefined
) {
const [value, setValue] = React.useState<string[]>(defaultValue);
const [errors, setErrors] = React.useState<string[] | undefined>();

const isInvalid = errors !== undefined;

return {
props: {
selectedOptions: value.map((val: string) => ({ label: val })),
onCreateOption: (newVal: any) => {
setValue([...value, newVal]);
},
onChange: (newVals: any[]) => {
setValue(newVals.map((val) => val.label));
onChange: (newSelectedOptions: any[]) => {
const newValues = newSelectedOptions.map((option) => option.label);
setValue(newValues);
if (errors && validate && validate(newValues) === undefined) {
setErrors(undefined);
}
},
isInvalid,
},
formRowProps: {
error: errors,
isInvalid,
},
value,
clear: () => {
setValue([]);
},
setValue,
validate: () => {
if (validate) {
const newErrors = validate(value);
setErrors(newErrors);

return newErrors === undefined;
}

return true;
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const PutOutputRequestSchema = {
outputId: schema.string(),
}),
body: schema.object({
hosts: schema.maybe(schema.arrayOf(schema.string())),
hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))),
ca_sha256: schema.maybe(schema.string()),
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const PutSettingsRequestSchema = {
body: schema.object({
agent_auto_upgrade: schema.maybe(schema.boolean()),
package_auto_upgrade: schema.maybe(schema.boolean()),
kibana_url: schema.maybe(schema.string()),
kibana_url: schema.maybe(schema.uri({ scheme: ['http', 'https'] })),
kibana_ca_sha256: schema.maybe(schema.string()),
}),
};

0 comments on commit 5ac3710

Please sign in to comment.