mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Uptime] Certificate expiration threshold settings (#63682)
* update settings * added cert form * update settings * update types * update test * updated tests * updated snapshots
This commit is contained in:
parent
d72de0ea16
commit
a9399c3d91
13 changed files with 600 additions and 123 deletions
|
@ -6,10 +6,20 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const DynamicSettingsType = t.type({
|
||||
heartbeatIndices: t.string,
|
||||
export const CertificatesStatesThresholdType = t.interface({
|
||||
warningState: t.number,
|
||||
errorState: t.number,
|
||||
});
|
||||
|
||||
export const DynamicSettingsType = t.intersection([
|
||||
t.type({
|
||||
heartbeatIndices: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
certificatesThresholds: CertificatesStatesThresholdType,
|
||||
}),
|
||||
]);
|
||||
|
||||
export const DynamicSettingsSaveType = t.intersection([
|
||||
t.type({
|
||||
success: t.boolean,
|
||||
|
@ -21,7 +31,12 @@ export const DynamicSettingsSaveType = t.intersection([
|
|||
|
||||
export type DynamicSettings = t.TypeOf<typeof DynamicSettingsType>;
|
||||
export type DynamicSettingsSaveResponse = t.TypeOf<typeof DynamicSettingsSaveType>;
|
||||
export type CertificatesStatesThreshold = t.TypeOf<typeof CertificatesStatesThresholdType>;
|
||||
|
||||
export const defaultDynamicSettings: DynamicSettings = {
|
||||
heartbeatIndices: 'heartbeat-8*',
|
||||
certificatesThresholds: {
|
||||
errorState: 7,
|
||||
warningState: 30,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CertificateForm shallow renders expected elements for valid props 1`] = `
|
||||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"history": Object {
|
||||
"action": "POP",
|
||||
"block": [Function],
|
||||
"canGo": [Function],
|
||||
"createHref": [Function],
|
||||
"entries": Array [
|
||||
Object {
|
||||
"hash": "",
|
||||
"key": "TestKeyForTesting",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"state": undefined,
|
||||
},
|
||||
],
|
||||
"go": [Function],
|
||||
"goBack": [Function],
|
||||
"goForward": [Function],
|
||||
"index": 0,
|
||||
"length": 1,
|
||||
"listen": [Function],
|
||||
"location": Object {
|
||||
"hash": "",
|
||||
"key": "TestKeyForTesting",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"state": undefined,
|
||||
},
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
},
|
||||
"location": Object {
|
||||
"hash": "",
|
||||
"key": "TestKeyForTesting",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"state": undefined,
|
||||
},
|
||||
"match": Object {
|
||||
"isExact": true,
|
||||
"params": Object {},
|
||||
"path": "/",
|
||||
"url": "/",
|
||||
},
|
||||
"staticContext": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CertificateExpirationForm
|
||||
fieldErrors={Object {}}
|
||||
formFields={
|
||||
Object {
|
||||
"certificatesThresholds": Object {
|
||||
"errorState": 7,
|
||||
"warningState": 36,
|
||||
},
|
||||
"heartbeatIndices": "heartbeat-8*",
|
||||
}
|
||||
}
|
||||
isDisabled={false}
|
||||
onChange={[MockFunction]}
|
||||
/>
|
||||
</ContextProvider>
|
||||
`;
|
|
@ -0,0 +1,69 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CertificateForm shallow renders expected elements for valid props 1`] = `
|
||||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"history": Object {
|
||||
"action": "POP",
|
||||
"block": [Function],
|
||||
"canGo": [Function],
|
||||
"createHref": [Function],
|
||||
"entries": Array [
|
||||
Object {
|
||||
"hash": "",
|
||||
"key": "TestKeyForTesting",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"state": undefined,
|
||||
},
|
||||
],
|
||||
"go": [Function],
|
||||
"goBack": [Function],
|
||||
"goForward": [Function],
|
||||
"index": 0,
|
||||
"length": 1,
|
||||
"listen": [Function],
|
||||
"location": Object {
|
||||
"hash": "",
|
||||
"key": "TestKeyForTesting",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"state": undefined,
|
||||
},
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
},
|
||||
"location": Object {
|
||||
"hash": "",
|
||||
"key": "TestKeyForTesting",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"state": undefined,
|
||||
},
|
||||
"match": Object {
|
||||
"isExact": true,
|
||||
"params": Object {},
|
||||
"path": "/",
|
||||
"url": "/",
|
||||
},
|
||||
"staticContext": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<IndicesForm
|
||||
fieldErrors={Object {}}
|
||||
formFields={
|
||||
Object {
|
||||
"certificatesThresholds": Object {
|
||||
"errorState": 7,
|
||||
"warningState": 36,
|
||||
},
|
||||
"heartbeatIndices": "heartbeat-8*",
|
||||
}
|
||||
}
|
||||
isDisabled={false}
|
||||
onChange={[MockFunction]}
|
||||
/>
|
||||
</ContextProvider>
|
||||
`;
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { CertificateExpirationForm } from '../certificate_form';
|
||||
import { shallowWithRouter } from '../../../lib';
|
||||
|
||||
describe('CertificateForm', () => {
|
||||
it('shallow renders expected elements for valid props', () => {
|
||||
expect(
|
||||
shallowWithRouter(
|
||||
<CertificateExpirationForm
|
||||
onChange={jest.fn()}
|
||||
formFields={{
|
||||
heartbeatIndices: 'heartbeat-8*',
|
||||
certificatesThresholds: { errorState: 7, warningState: 36 },
|
||||
}}
|
||||
fieldErrors={{}}
|
||||
isDisabled={false}
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { IndicesForm } from '../indices_form';
|
||||
import { shallowWithRouter } from '../../../lib';
|
||||
|
||||
describe('CertificateForm', () => {
|
||||
it('shallow renders expected elements for valid props', () => {
|
||||
expect(
|
||||
shallowWithRouter(
|
||||
<IndicesForm
|
||||
onChange={jest.fn()}
|
||||
formFields={{
|
||||
heartbeatIndices: 'heartbeat-8*',
|
||||
certificatesThresholds: { errorState: 7, warningState: 36 },
|
||||
}}
|
||||
fieldErrors={{}}
|
||||
isDisabled={false}
|
||||
/>
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
EuiDescribedFormGroup,
|
||||
EuiFormRow,
|
||||
EuiCode,
|
||||
EuiFieldNumber,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
EuiSelect,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { defaultDynamicSettings, DynamicSettings } from '../../../common/runtime_types';
|
||||
import { selectDynamicSettings } from '../../state/selectors';
|
||||
|
||||
type NumStr = string | number;
|
||||
|
||||
export type OnFieldChangeType = (field: string, value?: NumStr) => void;
|
||||
|
||||
export interface SettingsFormProps {
|
||||
onChange: OnFieldChangeType;
|
||||
formFields: DynamicSettings | null;
|
||||
fieldErrors: any;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
|
||||
export const CertificateExpirationForm: React.FC<SettingsFormProps> = ({
|
||||
onChange,
|
||||
formFields,
|
||||
fieldErrors,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const dss = useSelector(selectDynamicSettings);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.certificationSectionTitle"
|
||||
defaultMessage="Certificate Expiration"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.stateThresholds"
|
||||
defaultMessage="Expiration State Thresholds"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.stateThresholdsDescription"
|
||||
defaultMessage="Set certificate expiration warning/error thresholds"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={['errorState']}
|
||||
error={fieldErrors?.certificatesThresholds?.errorState}
|
||||
fullWidth
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.errorStateDefaultValue"
|
||||
defaultMessage="The default value is {defaultValue}"
|
||||
values={{
|
||||
defaultValue: (
|
||||
<EuiCode>{defaultDynamicSettings?.certificatesThresholds?.errorState}</EuiCode>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isInvalid={!!fieldErrors?.certificatesThresholds?.errorState}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.errorStateLabel"
|
||||
defaultMessage="Error state"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiFieldNumber
|
||||
data-test-subj={`error-state-threshold-input-${dss.loading ? 'loading' : 'loaded'}`}
|
||||
fullWidth
|
||||
disabled={isDisabled}
|
||||
isLoading={dss.loading}
|
||||
value={formFields?.certificatesThresholds?.errorState || ''}
|
||||
onChange={({ currentTarget: { value } }: any) =>
|
||||
onChange(
|
||||
'certificatesThresholds.errorState',
|
||||
value === '' ? undefined : Number(value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiSelect options={[{ value: 'day', text: 'Days' }]} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={['warningState']}
|
||||
error={fieldErrors?.certificatesThresholds?.warningState}
|
||||
fullWidth
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.warningStateDefaultValue"
|
||||
defaultMessage="The default value is {defaultValue}"
|
||||
values={{
|
||||
defaultValue: (
|
||||
<EuiCode>{defaultDynamicSettings?.certificatesThresholds?.warningState}</EuiCode>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isInvalid={!!fieldErrors?.certificatesThresholds?.warningState}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.warningStateLabel"
|
||||
defaultMessage="Warning state"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiFieldNumber
|
||||
data-test-subj={`warning-state-threshold-input-${
|
||||
dss.loading ? 'loading' : 'loaded'
|
||||
}`}
|
||||
fullWidth
|
||||
disabled={isDisabled}
|
||||
isLoading={dss.loading}
|
||||
value={formFields?.certificatesThresholds?.warningState || ''}
|
||||
onChange={(event: any) =>
|
||||
onChange('certificatesThresholds.warningState', Number(event.currentTarget.value))
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiSelect options={[{ value: 'day', text: 'Days' }]} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
EuiDescribedFormGroup,
|
||||
EuiFormRow,
|
||||
EuiCode,
|
||||
EuiFieldText,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { defaultDynamicSettings } from '../../../common/runtime_types';
|
||||
import { selectDynamicSettings } from '../../state/selectors';
|
||||
import { SettingsFormProps } from './certificate_form';
|
||||
|
||||
export const IndicesForm: React.FC<SettingsFormProps> = ({
|
||||
onChange,
|
||||
formFields,
|
||||
fieldErrors,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const dss = useSelector(selectDynamicSettings);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.indicesSectionTitle"
|
||||
defaultMessage="Indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesTitle"
|
||||
defaultMessage="Uptime indices"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesDescription"
|
||||
defaultMessage="Index pattern for matching indices that contain Heartbeat data"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={['heartbeatIndices']}
|
||||
error={fieldErrors?.heartbeatIndices}
|
||||
fullWidth
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesDefaultValue"
|
||||
defaultMessage="The default value is {defaultValue}"
|
||||
values={{
|
||||
defaultValue: <EuiCode>{defaultDynamicSettings.heartbeatIndices}</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isInvalid={!!fieldErrors?.heartbeatIndices}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesLabel"
|
||||
defaultMessage="Heartbeat indices"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj={`heartbeat-indices-input-${dss.loading ? 'loading' : 'loaded'}`}
|
||||
fullWidth
|
||||
disabled={isDisabled}
|
||||
isLoading={dss.loading}
|
||||
value={formFields?.heartbeatIndices || ''}
|
||||
onChange={(event: any) => onChange('heartbeatIndices', event.currentTarget.value)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -9,46 +9,54 @@ import {
|
|||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
EuiDescribedFormGroup,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { connect } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { cloneDeep, isEqual, set } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppState } from '../state';
|
||||
import { selectDynamicSettings } from '../state/selectors';
|
||||
import { DynamicSettingsState } from '../state/reducers/dynamic_settings';
|
||||
import { getDynamicSettings, setDynamicSettings } from '../state/actions/dynamic_settings';
|
||||
import { defaultDynamicSettings, DynamicSettings } from '../../common/runtime_types';
|
||||
import { DynamicSettings } from '../../common/runtime_types';
|
||||
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
|
||||
import { OVERVIEW_ROUTE } from '../../common/constants';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { UptimePage, useUptimeTelemetry } from '../hooks';
|
||||
import { IndicesForm } from '../components/settings/indices_form';
|
||||
import {
|
||||
CertificateExpirationForm,
|
||||
OnFieldChangeType,
|
||||
} from '../components/settings/certificate_form';
|
||||
|
||||
interface Props {
|
||||
dynamicSettingsState: DynamicSettingsState;
|
||||
}
|
||||
const getFieldErrors = (formFields: DynamicSettings | null) => {
|
||||
if (formFields) {
|
||||
const blankStr = 'May not be blank';
|
||||
const { certificatesThresholds, heartbeatIndices } = formFields;
|
||||
const heartbeatIndErr = heartbeatIndices.match(/^\S+$/) ? '' : blankStr;
|
||||
const errorStateErr = certificatesThresholds?.errorState ? null : blankStr;
|
||||
const warningStateErr = certificatesThresholds?.warningState ? null : blankStr;
|
||||
return {
|
||||
heartbeatIndices: heartbeatIndErr,
|
||||
certificatesThresholds:
|
||||
errorStateErr || warningStateErr
|
||||
? {
|
||||
errorState: errorStateErr,
|
||||
warningState: warningStateErr,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
dispatchGetDynamicSettings: typeof getDynamicSettings;
|
||||
dispatchSetDynamicSettings: typeof setDynamicSettings;
|
||||
}
|
||||
export const SettingsPage = () => {
|
||||
const dss = useSelector(selectDynamicSettings);
|
||||
|
||||
export const SettingsPageComponent = ({
|
||||
dynamicSettingsState: dss,
|
||||
dispatchGetDynamicSettings,
|
||||
dispatchSetDynamicSettings,
|
||||
}: Props & DispatchProps) => {
|
||||
const settingsBreadcrumbText = i18n.translate('xpack.uptime.settingsBreadcrumbText', {
|
||||
defaultMessage: 'Settings',
|
||||
});
|
||||
|
@ -56,9 +64,11 @@ export const SettingsPageComponent = ({
|
|||
|
||||
useUptimeTelemetry(UptimePage.Settings);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatchGetDynamicSettings({});
|
||||
}, [dispatchGetDynamicSettings]);
|
||||
dispatch(getDynamicSettings({}));
|
||||
}, [dispatch]);
|
||||
|
||||
const [formFields, setFormFields] = useState<DynamicSettings | null>(dss.settings || null);
|
||||
|
||||
|
@ -66,22 +76,22 @@ export const SettingsPageComponent = ({
|
|||
setFormFields({ ...dss.settings });
|
||||
}
|
||||
|
||||
const fieldErrors = formFields && {
|
||||
heartbeatIndices: formFields.heartbeatIndices.match(/^\S+$/) ? null : 'May not be blank',
|
||||
};
|
||||
const fieldErrors = getFieldErrors(formFields);
|
||||
|
||||
const isFormValid = !(fieldErrors && Object.values(fieldErrors).find(v => !!v));
|
||||
|
||||
const onChangeFormField = (field: keyof DynamicSettings, value: any) => {
|
||||
const onChangeFormField: OnFieldChangeType = (field, value) => {
|
||||
if (formFields) {
|
||||
formFields[field] = value;
|
||||
setFormFields({ ...formFields });
|
||||
const newFormFields = cloneDeep(formFields);
|
||||
set(newFormFields, field, value);
|
||||
setFormFields(cloneDeep(newFormFields));
|
||||
}
|
||||
};
|
||||
|
||||
const onApply = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (formFields) {
|
||||
dispatchSetDynamicSettings(formFields);
|
||||
dispatch(setDynamicSettings(formFields));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -128,68 +138,18 @@ export const SettingsPageComponent = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<form onSubmit={onApply}>
|
||||
<EuiForm>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.indicesSectionTitle"
|
||||
defaultMessage="Indices"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesTitle"
|
||||
defaultMessage="Uptime indices"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesDescription"
|
||||
defaultMessage="Index pattern for matching indices that contain Heartbeat data"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={['heartbeatIndices']}
|
||||
error={fieldErrors?.heartbeatIndices}
|
||||
fullWidth
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesDefaultValue"
|
||||
defaultMessage="The default value is {defaultValue}"
|
||||
values={{
|
||||
defaultValue: (
|
||||
<EuiCode>{defaultDynamicSettings.heartbeatIndices}</EuiCode>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isInvalid={!!fieldErrors?.heartbeatIndices}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.sourceConfiguration.heartbeatIndicesLabel"
|
||||
defaultMessage="Heartbeat indices"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj={`heartbeat-indices-input-${
|
||||
dss.loading ? 'loading' : 'loaded'
|
||||
}`}
|
||||
fullWidth
|
||||
disabled={isFormDisabled}
|
||||
isLoading={dss.loading}
|
||||
value={formFields?.heartbeatIndices || ''}
|
||||
onChange={(event: any) =>
|
||||
onChangeFormField('heartbeatIndices', event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
<IndicesForm
|
||||
onChange={onChangeFormField}
|
||||
formFields={formFields}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
<CertificateExpirationForm
|
||||
onChange={onChangeFormField}
|
||||
formFields={formFields}
|
||||
fieldErrors={fieldErrors}
|
||||
isDisabled={isFormDisabled}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
|
@ -230,18 +190,3 @@ export const SettingsPageComponent = ({
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
dynamicSettingsState: selectDynamicSettings(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
dispatchGetDynamicSettings: () => {
|
||||
return dispatch(getDynamicSettings({}));
|
||||
},
|
||||
dispatchSetDynamicSettings: (settings: DynamicSettings) => {
|
||||
return dispatch(setDynamicSettings(settings));
|
||||
},
|
||||
});
|
||||
|
||||
export const SettingsPage = connect(mapStateToProps, mapDispatchToProps)(SettingsPageComponent);
|
||||
|
|
|
@ -88,6 +88,10 @@ describe('status check alert', () => {
|
|||
Object {
|
||||
"callES": [MockFunction],
|
||||
"dynamicSettings": Object {
|
||||
"certificatesThresholds": Object {
|
||||
"errorState": 7,
|
||||
"warningState": 30,
|
||||
},
|
||||
"heartbeatIndices": "heartbeat-8*",
|
||||
},
|
||||
"locations": Array [],
|
||||
|
@ -131,6 +135,10 @@ describe('status check alert', () => {
|
|||
Object {
|
||||
"callES": [MockFunction],
|
||||
"dynamicSettings": Object {
|
||||
"certificatesThresholds": Object {
|
||||
"errorState": 7,
|
||||
"warningState": 30,
|
||||
},
|
||||
"heartbeatIndices": "heartbeat-8*",
|
||||
},
|
||||
"locations": Array [],
|
||||
|
|
|
@ -7,14 +7,10 @@
|
|||
import {
|
||||
DynamicSettings,
|
||||
defaultDynamicSettings,
|
||||
} from '../../../../legacy/plugins/uptime/common/runtime_types/dynamic_settings';
|
||||
} from '../../../../legacy/plugins/uptime/common/runtime_types';
|
||||
import { SavedObjectsType, SavedObjectsErrorHelpers } from '../../../../../src/core/server';
|
||||
import { UMSavedObjectsQueryFn } from './adapters';
|
||||
|
||||
export interface UMDynamicSettingsType {
|
||||
heartbeatIndices: string;
|
||||
}
|
||||
|
||||
export interface UMSavedObjectsAdapter {
|
||||
getUptimeDynamicSettings: UMSavedObjectsQueryFn<DynamicSettings>;
|
||||
setUptimeDynamicSettings: UMSavedObjectsQueryFn<void, DynamicSettings>;
|
||||
|
@ -32,6 +28,16 @@ export const umDynamicSettings: SavedObjectsType = {
|
|||
heartbeatIndices: {
|
||||
type: 'keyword',
|
||||
},
|
||||
certificatesThresholds: {
|
||||
properties: {
|
||||
errorState: {
|
||||
type: 'long',
|
||||
},
|
||||
warningState: {
|
||||
type: 'long',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -18,7 +18,13 @@ export default function({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('can change the settings', async () => {
|
||||
const newSettings = { heartbeatIndices: 'myIndex1*' };
|
||||
const newSettings = {
|
||||
heartbeatIndices: 'myIndex1*',
|
||||
certificatesThresholds: {
|
||||
errorState: 5,
|
||||
warningState: 15,
|
||||
},
|
||||
};
|
||||
const postResponse = await supertest
|
||||
.post(`/api/uptime/dynamic_settings`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
|
|
@ -74,7 +74,41 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
// Verify that the settings page shows the value we previously saved
|
||||
await settings.go();
|
||||
const fields = await settings.loadFields();
|
||||
expect(fields).to.eql(newFieldValues);
|
||||
expect(fields.heartbeatIndices).to.eql(newFieldValues.heartbeatIndices);
|
||||
});
|
||||
|
||||
it('changing certificate expiration error threshold is reflected in settings page', async () => {
|
||||
const settings = uptimeService.settings;
|
||||
|
||||
await settings.go();
|
||||
|
||||
const newErrorThreshold = '5';
|
||||
await settings.changeErrorThresholdInput(newErrorThreshold);
|
||||
await settings.apply();
|
||||
|
||||
await uptimePage.goToRoot();
|
||||
|
||||
// Verify that the settings page shows the value we previously saved
|
||||
await settings.go();
|
||||
const fields = await settings.loadFields();
|
||||
expect(fields.certificatesThresholds.errorState).to.eql(newErrorThreshold);
|
||||
});
|
||||
|
||||
it('changing certificate expiration warning threshold is reflected in settings page', async () => {
|
||||
const settings = uptimeService.settings;
|
||||
|
||||
await settings.go();
|
||||
|
||||
const newWarningThreshold = '15';
|
||||
await settings.changeWarningThresholdInput(newWarningThreshold);
|
||||
await settings.apply();
|
||||
|
||||
await uptimePage.goToRoot();
|
||||
|
||||
// Verify that the settings page shows the value we previously saved
|
||||
await settings.go();
|
||||
const fields = await settings.loadFields();
|
||||
expect(fields.certificatesThresholds.warningState).to.eql(newWarningThreshold);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,20 +10,41 @@ export function UptimeSettingsProvider({ getService }: FtrProviderContext) {
|
|||
const testSubjects = getService('testSubjects');
|
||||
const retry = getService('retry');
|
||||
|
||||
const changeInputField = async (text: string, field: string) => {
|
||||
const input = await testSubjects.find(field, 5000);
|
||||
await input.clearValueWithKeyboard();
|
||||
await input.type(text);
|
||||
};
|
||||
|
||||
return {
|
||||
go: async () => {
|
||||
await testSubjects.click('settings-page-link', 5000);
|
||||
},
|
||||
|
||||
changeHeartbeatIndicesInput: async (text: string) => {
|
||||
const input = await testSubjects.find('heartbeat-indices-input-loaded', 5000);
|
||||
await input.clearValueWithKeyboard();
|
||||
await input.type(text);
|
||||
await changeInputField(text, 'heartbeat-indices-input-loaded');
|
||||
},
|
||||
changeErrorThresholdInput: async (text: string) => {
|
||||
await changeInputField(text, 'error-state-threshold-input-loaded');
|
||||
},
|
||||
changeWarningThresholdInput: async (text: string) => {
|
||||
await changeInputField(text, 'warning-state-threshold-input-loaded');
|
||||
},
|
||||
loadFields: async () => {
|
||||
const input = await testSubjects.find('heartbeat-indices-input-loaded', 5000);
|
||||
const heartbeatIndices = await input.getAttribute('value');
|
||||
const indInput = await testSubjects.find('heartbeat-indices-input-loaded', 5000);
|
||||
const errorInput = await testSubjects.find('error-state-threshold-input-loaded', 5000);
|
||||
const warningInput = await testSubjects.find('warning-state-threshold-input-loaded', 5000);
|
||||
const heartbeatIndices = await indInput.getAttribute('value');
|
||||
const errorThreshold = await errorInput.getAttribute('value');
|
||||
const warningThreshold = await warningInput.getAttribute('value');
|
||||
|
||||
return { heartbeatIndices };
|
||||
return {
|
||||
heartbeatIndices,
|
||||
certificatesThresholds: {
|
||||
errorState: errorThreshold,
|
||||
warningState: warningThreshold,
|
||||
},
|
||||
};
|
||||
},
|
||||
applyButtonIsDisabled: async () => {
|
||||
return !!(await (await testSubjects.find('apply-settings-button')).getAttribute('disabled'));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue