[Uptime] Update TLS settings (#64111)

* Refactor settings form event handling and modify certs fields.

* Fix/improve broken types/unit/integration/api tests.

* Modify default expiration threshold.

* Rename test vars.

* Implement PR feedback.

* Refresh snapshots, fix broken tests/types.

* Remove unnecessary state spreading.

* Add type for settings field errors.

* Refresh test snapshots.

* Improve punctuation.
This commit is contained in:
Justin Kambic 2020-04-24 12:53:28 -04:00 committed by GitHub
parent a012ddf9df
commit e918c43535
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 382 additions and 336 deletions

View file

@ -9,6 +9,7 @@ export { CHART_FORMAT_LIMITS } from './chart_format_limits';
export { CLIENT_DEFAULTS } from './client_defaults';
export { CONTEXT_DEFAULTS } from './context_defaults';
export * from './capabilities';
export * from './settings_defaults';
export { PLUGIN } from './plugin';
export { QUERY, STATES } from './query';
export * from './ui';

View file

@ -0,0 +1,15 @@
/*
* 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 { DynamicSettings } from '../runtime_types';
export const DYNAMIC_SETTINGS_DEFAULTS: DynamicSettings = {
heartbeatIndices: 'heartbeat-8*',
certThresholds: {
expiration: 30,
age: 365,
},
};

View file

@ -6,19 +6,15 @@
import * as t from 'io-ts';
export const CertificatesStatesThresholdType = t.interface({
warningState: t.number,
errorState: t.number,
export const CertStateThresholdsType = t.type({
age: t.number,
expiration: t.number,
});
export const DynamicSettingsType = t.intersection([
t.type({
heartbeatIndices: t.string,
}),
t.partial({
certificatesThresholds: CertificatesStatesThresholdType,
}),
]);
export const DynamicSettingsType = t.type({
heartbeatIndices: t.string,
certThresholds: CertStateThresholdsType,
});
export const DynamicSettingsSaveType = t.intersection([
t.type({
@ -31,12 +27,4 @@ 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,
},
};
export type CertStateThresholds = t.TypeOf<typeof CertStateThresholdsType>;

View file

@ -52,17 +52,18 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] =
}
>
<CertificateExpirationForm
fieldErrors={Object {}}
fieldErrors={null}
formFields={
Object {
"certificatesThresholds": Object {
"errorState": 7,
"warningState": 36,
"certThresholds": Object {
"age": 36,
"expiration": 7,
},
"heartbeatIndices": "heartbeat-8*",
}
}
isDisabled={false}
loading={false}
onChange={[MockFunction]}
/>
</ContextProvider>

View file

@ -52,17 +52,18 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] =
}
>
<IndicesForm
fieldErrors={Object {}}
fieldErrors={null}
formFields={
Object {
"certificatesThresholds": Object {
"errorState": 7,
"warningState": 36,
"certThresholds": Object {
"age": 36,
"expiration": 7,
},
"heartbeatIndices": "heartbeat-8*",
}
}
isDisabled={false}
loading={false}
onChange={[MockFunction]}
/>
</ContextProvider>

View file

@ -13,12 +13,13 @@ describe('CertificateForm', () => {
expect(
shallowWithRouter(
<CertificateExpirationForm
loading={false}
onChange={jest.fn()}
formFields={{
heartbeatIndices: 'heartbeat-8*',
certificatesThresholds: { errorState: 7, warningState: 36 },
certThresholds: { expiration: 7, age: 36 },
}}
fieldErrors={{}}
fieldErrors={null}
isDisabled={false}
/>
)

View file

@ -13,12 +13,13 @@ describe('CertificateForm', () => {
expect(
shallowWithRouter(
<IndicesForm
loading={false}
onChange={jest.fn()}
formFields={{
heartbeatIndices: 'heartbeat-8*',
certificatesThresholds: { errorState: 7, warningState: 36 },
certThresholds: { expiration: 7, age: 36 },
}}
fieldErrors={{}}
fieldErrors={null}
isDisabled={false}
/>
)

View file

@ -6,155 +6,157 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { useSelector } from 'react-redux';
import {
EuiDescribedFormGroup,
EuiFormRow,
EuiCode,
EuiFieldNumber,
EuiText,
EuiTitle,
EuiSpacer,
EuiSelect,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { defaultDynamicSettings, DynamicSettings } from '../../../common/runtime_types';
import { selectDynamicSettings } from '../../state/selectors';
import { CertStateThresholds } from '../../../common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
import { SettingsFormProps } from '../../pages/settings';
type NumStr = string | number;
export type OnFieldChangeType = (field: string, value?: NumStr) => void;
export interface SettingsFormProps {
onChange: OnFieldChangeType;
formFields: DynamicSettings | null;
fieldErrors: any;
isDisabled: boolean;
interface ChangedValues {
heartbeatIndices?: string;
certThresholds?: Partial<CertStateThresholds>;
}
export type OnFieldChangeType = (changedValues: ChangedValues) => void;
export const CertificateExpirationForm: React.FC<SettingsFormProps> = ({
loading,
onChange,
formFields,
fieldErrors,
isDisabled,
}) => {
const dss = useSelector(selectDynamicSettings);
return (
<>
<EuiTitle size="s">
<h3>
}) => (
<>
<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.certificationSectionTitle"
defaultMessage="Certificate Expiration"
id="xpack.uptime.sourceConfiguration.expirationThreshold"
defaultMessage="Expiration/Age Thresholds"
/>
</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiDescribedFormGroup
title={
<h4>
<FormattedMessage
id="xpack.uptime.sourceConfiguration.stateThresholds"
defaultMessage="Expiration State Thresholds"
/>
</h4>
}
description={
</h4>
}
description={
<FormattedMessage
id="xpack.uptime.sourceConfiguration.certificateThresholdDescription"
defaultMessage="Change the threshold for displaying and alerting on certificate errors. Note: this will affect any configured alerts."
/>
}
>
<EuiFormRow
describedByIds={['errorState']}
error={fieldErrors?.certificatesThresholds?.expirationThresholdError}
fullWidth
helpText={
<FormattedMessage
id="xpack.uptime.sourceConfiguration.stateThresholdsDescription"
defaultMessage="Set certificate expiration warning/error thresholds"
id="xpack.uptime.sourceConfiguration.expirationThresholdDefaultValue"
defaultMessage="The default value is {defaultValue}"
values={{
defaultValue: (
<EuiCode>{DYNAMIC_SETTINGS_DEFAULTS.certThresholds.expiration}</EuiCode>
),
}}
/>
}
isInvalid={!!fieldErrors?.certificatesThresholds?.expirationThresholdError}
label={
<FormattedMessage
id="xpack.uptime.sourceConfiguration.errorStateLabel"
defaultMessage="Expiration threshold"
/>
}
>
<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>
),
}}
<EuiFlexGroup>
<EuiFlexItem grow={2}>
<EuiFieldNumber
data-test-subj={`expiration-threshold-input-${loading ? 'loading' : 'loaded'}`}
fullWidth
disabled={isDisabled}
isLoading={loading}
value={formFields?.certThresholds?.expiration || ''}
onChange={e =>
onChange({
certThresholds: {
expiration: Number(e.target.value),
},
})
}
/>
}
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}>
<EuiText>
<FormattedMessage
id="xpack.uptime.sourceConfiguration.ageLimit.units.days"
defaultMessage="Days"
/>
</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>
),
}}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
<EuiFormRow
describedByIds={['warningState']}
error={fieldErrors?.certificatesThresholds?.ageThresholdError}
fullWidth
helpText={
<FormattedMessage
id="xpack.uptime.sourceConfiguration.ageThresholdDefaultValue"
defaultMessage="The default value is {defaultValue}"
values={{
defaultValue: <EuiCode>{DYNAMIC_SETTINGS_DEFAULTS.certThresholds.age}</EuiCode>,
}}
/>
}
isInvalid={!!fieldErrors?.certificatesThresholds?.ageThresholdError}
label={
<FormattedMessage
id="xpack.uptime.sourceConfiguration.warningStateLabel"
defaultMessage="Age limit"
/>
}
>
<EuiFlexGroup>
<EuiFlexItem grow={2}>
<EuiFieldNumber
data-test-subj={`age-threshold-input-${loading ? 'loading' : 'loaded'}`}
fullWidth
disabled={isDisabled}
isLoading={loading}
value={formFields?.certThresholds?.age || ''}
onChange={e =>
onChange({
certThresholds: { age: Number(e.currentTarget.value) },
})
}
/>
}
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}>
<EuiText>
<FormattedMessage
id="xpack.uptime.sourceConfiguration.ageLimit.units.days"
defaultMessage="Days"
/>
</EuiFlexItem>
<EuiFlexItem grow={1}>
<EuiSelect options={[{ value: 'day', text: 'Days' }]} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
</EuiDescribedFormGroup>
</>
);
};
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
</EuiDescribedFormGroup>
</>
);

View file

@ -6,7 +6,6 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { useSelector } from 'react-redux';
import {
EuiDescribedFormGroup,
EuiFormRow,
@ -15,76 +14,72 @@ import {
EuiTitle,
EuiSpacer,
} from '@elastic/eui';
import { defaultDynamicSettings } from '../../../common/runtime_types';
import { selectDynamicSettings } from '../../state/selectors';
import { SettingsFormProps } from './certificate_form';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants';
import { SettingsFormProps } from '../../pages/settings';
export const IndicesForm: React.FC<SettingsFormProps> = ({
onChange,
loading,
formFields,
fieldErrors,
isDisabled,
}) => {
const dss = useSelector(selectDynamicSettings);
return (
<>
<EuiTitle size="s">
<h3>
}) => (
<>
<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.indicesSectionTitle"
defaultMessage="Indices"
id="xpack.uptime.sourceConfiguration.heartbeatIndicesTitle"
defaultMessage="Uptime indices"
/>
</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiDescribedFormGroup
title={
<h4>
<FormattedMessage
id="xpack.uptime.sourceConfiguration.heartbeatIndicesTitle"
defaultMessage="Uptime indices"
/>
</h4>
}
description={
</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.heartbeatIndicesDescription"
defaultMessage="Index pattern for matching indices that contain Heartbeat data"
id="xpack.uptime.sourceConfiguration.heartbeatIndicesDefaultValue"
defaultMessage="The default value is {defaultValue}"
values={{
defaultValue: <EuiCode>{DYNAMIC_SETTINGS_DEFAULTS.heartbeatIndices}</EuiCode>,
}}
/>
}
isInvalid={!!fieldErrors?.heartbeatIndices}
label={
<FormattedMessage
id="xpack.uptime.sourceConfiguration.heartbeatIndicesLabel"
defaultMessage="Heartbeat indices"
/>
}
>
<EuiFormRow
describedByIds={['heartbeatIndices']}
error={fieldErrors?.heartbeatIndices}
<EuiFieldText
data-test-subj={`heartbeat-indices-input-${loading ? 'loading' : 'loaded'}`}
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>
</>
);
};
disabled={isDisabled}
isLoading={loading}
value={formFields?.heartbeatIndices || ''}
onChange={(event: any) => onChange({ heartbeatIndices: event.currentTarget.value })}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
</>
);

View file

@ -17,8 +17,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useDispatch, useSelector } from 'react-redux';
import { cloneDeep, isEqual, set } from 'lodash';
import { i18n } from '@kbn/i18n';
import { isEqual } from 'lodash';
import { Link } from 'react-router-dom';
import { selectDynamicSettings } from '../state/selectors';
import { getDynamicSettings, setDynamicSettings } from '../state/actions/dynamic_settings';
@ -32,21 +31,38 @@ import {
CertificateExpirationForm,
OnFieldChangeType,
} from '../components/settings/certificate_form';
import * as Translations from './translations';
const getFieldErrors = (formFields: DynamicSettings | null) => {
interface SettingsPageFieldErrors {
heartbeatIndices: 'May not be blank' | '';
certificatesThresholds: {
expirationThresholdError: string | null;
ageThresholdError: string | null;
} | null;
}
export interface SettingsFormProps {
loading: boolean;
onChange: OnFieldChangeType;
formFields: DynamicSettings | null;
fieldErrors: SettingsPageFieldErrors | null;
isDisabled: boolean;
}
const getFieldErrors = (formFields: DynamicSettings | null): SettingsPageFieldErrors | null => {
if (formFields) {
const blankStr = 'May not be blank';
const { certificatesThresholds, heartbeatIndices } = formFields;
const { certThresholds: certificatesThresholds, heartbeatIndices } = formFields;
const heartbeatIndErr = heartbeatIndices.match(/^\S+$/) ? '' : blankStr;
const errorStateErr = certificatesThresholds?.errorState ? null : blankStr;
const warningStateErr = certificatesThresholds?.warningState ? null : blankStr;
const expirationThresholdError = certificatesThresholds?.expiration ? null : blankStr;
const ageThresholdError = certificatesThresholds?.age ? null : blankStr;
return {
heartbeatIndices: heartbeatIndErr,
certificatesThresholds:
errorStateErr || warningStateErr
expirationThresholdError || ageThresholdError
? {
errorState: errorStateErr,
warningState: warningStateErr,
expirationThresholdError,
ageThresholdError,
}
: null,
};
@ -57,10 +73,7 @@ const getFieldErrors = (formFields: DynamicSettings | null) => {
export const SettingsPage = () => {
const dss = useSelector(selectDynamicSettings);
const settingsBreadcrumbText = i18n.translate('xpack.uptime.settingsBreadcrumbText', {
defaultMessage: 'Settings',
});
useBreadcrumbs([{ text: settingsBreadcrumbText }]);
useBreadcrumbs([{ text: Translations.settings.breadcrumbText }]);
useUptimeTelemetry(UptimePage.Settings);
@ -70,21 +83,28 @@ export const SettingsPage = () => {
dispatch(getDynamicSettings());
}, [dispatch]);
const [formFields, setFormFields] = useState<DynamicSettings | null>(dss.settings || null);
const [formFields, setFormFields] = useState<DynamicSettings | null>(
dss.settings ? { ...dss.settings } : null
);
if (!dss.loadError && formFields == null && dss.settings) {
setFormFields({ ...dss.settings });
if (!dss.loadError && formFields === null && dss.settings) {
setFormFields(Object.assign({}, { ...dss.settings }));
}
const fieldErrors = getFieldErrors(formFields);
const isFormValid = !(fieldErrors && Object.values(fieldErrors).find(v => !!v));
const onChangeFormField: OnFieldChangeType = (field, value) => {
const onChangeFormField: OnFieldChangeType = changedField => {
if (formFields) {
const newFormFields = cloneDeep(formFields);
set(newFormFields, field, value);
setFormFields(cloneDeep(newFormFields));
setFormFields({
heartbeatIndices: changedField.heartbeatIndices ?? formFields.heartbeatIndices,
certThresholds: Object.assign(
{},
formFields.certThresholds,
changedField?.certThresholds ?? null
),
});
}
};
@ -95,27 +115,18 @@ export const SettingsPage = () => {
}
};
const resetForm = () => {
if (formFields && dss.settings) {
setFormFields({ ...dss.settings });
}
};
const resetForm = () => setFormFields(dss.settings ? { ...dss.settings } : null);
const isFormDirty = dss.settings ? !isEqual(dss.settings, formFields) : true;
const isFormDirty = !isEqual(dss.settings, formFields);
const canEdit: boolean =
!!useKibana().services?.application?.capabilities.uptime.configureSettings || false;
const isFormDisabled = dss.loading || !canEdit;
const editNoticeTitle = i18n.translate('xpack.uptime.settings.cannotEditTitle', {
defaultMessage: 'You do not have permission to edit settings.',
});
const editNoticeText = i18n.translate('xpack.uptime.settings.cannotEditText', {
defaultMessage:
"Your user currently has 'Read' permissions for the Uptime app. Enable a permissions-level of 'All' to edit these settings.",
});
const cannotEditNotice = canEdit ? null : (
<>
<EuiCallOut title={editNoticeTitle}>{editNoticeText}</EuiCallOut>
<EuiCallOut title={Translations.settings.editNoticeTitle}>
{Translations.settings.editNoticeText}
</EuiCallOut>
<EuiSpacer size="s" />
</>
);
@ -124,9 +135,7 @@ export const SettingsPage = () => {
<>
<Link to={OVERVIEW_ROUTE} data-test-subj="uptimeSettingsToOverviewLink">
<EuiButtonEmpty size="s" color="primary" iconType="arrowLeft">
{i18n.translate('xpack.uptime.settings.returnToOverviewLinkLabel', {
defaultMessage: 'Return to overview',
})}
{Translations.settings.returnToOverviewLinkLabel}
</EuiButtonEmpty>
</Link>
<EuiSpacer size="s" />
@ -139,12 +148,14 @@ export const SettingsPage = () => {
<form onSubmit={onApply}>
<EuiForm>
<IndicesForm
loading={dss.loading}
onChange={onChangeFormField}
formFields={formFields}
fieldErrors={fieldErrors}
isDisabled={isFormDisabled}
/>
<CertificateExpirationForm
loading={dss.loading}
onChange={onChangeFormField}
formFields={formFields}
fieldErrors={fieldErrors}

View file

@ -0,0 +1,23 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const settings = {
breadcrumbText: i18n.translate('xpack.uptime.settingsBreadcrumbText', {
defaultMessage: 'Settings',
}),
editNoticeTitle: i18n.translate('xpack.uptime.settings.cannotEditTitle', {
defaultMessage: 'You do not have permission to edit settings.',
}),
editNoticeText: i18n.translate('xpack.uptime.settings.cannotEditText', {
defaultMessage:
"Your user currently has 'Read' permissions for the Uptime app. Enable a permissions-level of 'All' to edit these settings.",
}),
returnToOverviewLinkLabel: i18n.translate('xpack.uptime.settings.returnToOverviewLinkLabel', {
defaultMessage: 'Return to overview',
}),
};

View file

@ -31,23 +31,19 @@ export const dynamicSettingsReducer = handleActions<DynamicSettingsState, any>(
...state,
loading: true,
}),
[String(getDynamicSettingsSuccess)]: (state, action: Action<DynamicSettings>) => {
return {
loading: false,
settings: action.payload,
};
},
[String(getDynamicSettingsFail)]: (state, action: Action<Error>) => {
return {
loading: false,
loadError: action.payload,
};
},
[String(getDynamicSettingsSuccess)]: (_state, action: Action<DynamicSettings>) => ({
loading: false,
settings: action.payload,
}),
[String(getDynamicSettingsFail)]: (_state, action: Action<Error>) => ({
loading: false,
loadError: action.payload,
}),
[String(setDynamicSettings)]: state => ({
...state,
loading: true,
}),
[String(setDynamicSettingsSuccess)]: (state, action: Action<DynamicSettings>) => ({
[String(setDynamicSettingsSuccess)]: (_state, action: Action<DynamicSettings>) => ({
settings: action.payload,
saveSucceded: true,
loading: false,

View file

@ -6,6 +6,7 @@
import { getBasePath, isIntegrationsPopupOpen } from '../index';
import { AppState } from '../../../state';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants';
describe('state selectors', () => {
const state: AppState = {
@ -20,6 +21,7 @@ describe('state selectors', () => {
loading: false,
},
dynamicSettings: {
settings: DYNAMIC_SETTINGS_DEFAULTS,
loading: false,
},
monitor: {

View file

@ -24,9 +24,7 @@ export const monitorLocationsSelector = (state: AppState, monitorId: string) =>
export const monitorStatusSelector = (state: AppState) => state.monitorStatus.status;
export const selectDynamicSettings = (state: AppState) => {
return state.dynamicSettings;
};
export const selectDynamicSettings = (state: AppState) => state.dynamicSettings;
export const selectIndexPattern = ({ indexPattern }: AppState) => {
return { indexPattern: indexPattern.index_pattern, loading: indexPattern.loading };

View file

@ -16,7 +16,7 @@ import { AlertType } from '../../../../../alerting/server';
import { IRouter } from 'kibana/server';
import { UMServerLibs } from '../../lib';
import { UptimeCoreSetup } from '../../adapters';
import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants';
import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks';
/**
@ -52,7 +52,7 @@ const mockOptions = (
id: '',
type: '',
references: [],
attributes: defaultDynamicSettings,
attributes: DYNAMIC_SETTINGS_DEFAULTS,
});
return {
params,
@ -88,9 +88,9 @@ describe('status check alert', () => {
Object {
"callES": [MockFunction],
"dynamicSettings": Object {
"certificatesThresholds": Object {
"errorState": 7,
"warningState": 30,
"certThresholds": Object {
"age": 365,
"expiration": 30,
},
"heartbeatIndices": "heartbeat-8*",
},
@ -135,9 +135,9 @@ describe('status check alert', () => {
Object {
"callES": [MockFunction],
"dynamicSettings": Object {
"certificatesThresholds": Object {
"errorState": 7,
"warningState": 30,
"certThresholds": Object {
"age": 365,
"expiration": 30,
},
"heartbeatIndices": "heartbeat-8*",
},

View file

@ -5,6 +5,7 @@
*/
import { getCerts } from '../get_certs';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants';
describe('getCerts', () => {
let mockHits: any;
@ -86,7 +87,10 @@ describe('getCerts', () => {
it('parses query result and returns expected values', async () => {
const result = await getCerts({
callES: mockCallES,
dynamicSettings: { heartbeatIndices: 'heartbeat*' },
dynamicSettings: {
heartbeatIndices: 'heartbeat*',
certThresholds: DYNAMIC_SETTINGS_DEFAULTS.certThresholds,
},
index: 1,
from: 'now-2d',
to: 'now+1h',

View file

@ -5,14 +5,14 @@
*/
import { getLatestMonitor } from '../get_latest_monitor';
import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants';
describe('getLatestMonitor', () => {
let expectedGetLatestSearchParams: any;
let mockEsSearchResult: any;
beforeEach(() => {
expectedGetLatestSearchParams = {
index: defaultDynamicSettings.heartbeatIndices,
index: DYNAMIC_SETTINGS_DEFAULTS.heartbeatIndices,
body: {
query: {
bool: {
@ -64,7 +64,7 @@ describe('getLatestMonitor', () => {
const mockEsClient = jest.fn(async (_request: any, _params: any) => mockEsSearchResult);
const result = await getLatestMonitor({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
dateStart: 'now-1h',
dateEnd: 'now',
monitorId: 'testMonitor',

View file

@ -7,7 +7,7 @@
import { set } from 'lodash';
import mockChartsData from './monitor_charts_mock.json';
import { getMonitorDurationChart } from '../get_monitor_duration';
import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants';
describe('ElasticsearchMonitorsAdapter', () => {
it('getMonitorChartsData will provide expected filters', async () => {
@ -16,7 +16,7 @@ describe('ElasticsearchMonitorsAdapter', () => {
const search = searchMock.bind({});
await getMonitorDurationChart({
callES: search,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
monitorId: 'fooID',
dateStart: 'now-15m',
dateEnd: 'now',
@ -39,7 +39,7 @@ describe('ElasticsearchMonitorsAdapter', () => {
expect(
await getMonitorDurationChart({
callES: search,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
monitorId: 'id',
dateStart: 'now-15m',
dateEnd: 'now',

View file

@ -6,8 +6,8 @@
import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks';
import { getMonitorStatus } from '../get_monitor_status';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants';
import { ScopedClusterClient } from 'src/core/server';
import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
interface BucketItemCriteria {
monitor_id: string;
@ -103,7 +103,7 @@ describe('getMonitorStatus', () => {
}`;
await getMonitorStatus({
callES,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
filters: exampleFilter,
locations: [],
numTimes: 5,
@ -206,7 +206,7 @@ describe('getMonitorStatus', () => {
const [callES, esMock] = setupMock([]);
await getMonitorStatus({
callES,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
locations: ['fairbanks', 'harrisburg'],
numTimes: 1,
timerange: {
@ -329,7 +329,7 @@ describe('getMonitorStatus', () => {
};
const result = await getMonitorStatus({
callES,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
...clientParameters,
});
expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1);
@ -494,7 +494,7 @@ describe('getMonitorStatus', () => {
const [callES] = setupMock(criteria);
const result = await getMonitorStatus({
callES,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
locations: [],
numTimes: 5,
timerange: {

View file

@ -5,7 +5,7 @@
*/
import { getPingHistogram } from '../get_ping_histogram';
import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants';
describe('getPingHistogram', () => {
const standardMockResponse: any = {
@ -59,7 +59,7 @@ describe('getPingHistogram', () => {
const result = await getPingHistogram({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
from: 'now-15m',
to: 'now',
filters: null,
@ -78,7 +78,7 @@ describe('getPingHistogram', () => {
const result = await getPingHistogram({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
from: 'now-15m',
to: 'now',
filters: null,
@ -140,7 +140,7 @@ describe('getPingHistogram', () => {
const result = await getPingHistogram({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
from: '1234',
to: '5678',
filters: JSON.stringify(searchFilter),
@ -196,7 +196,7 @@ describe('getPingHistogram', () => {
const filters = `{"bool":{"must":[{"simple_query_string":{"query":"http"}}]}}`;
const result = await getPingHistogram({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
from: 'now-15m',
to: 'now',
filters,
@ -213,7 +213,7 @@ describe('getPingHistogram', () => {
mockEsClient.mockReturnValue(standardMockResponse);
const result = await getPingHistogram({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
from: '1234',
to: '5678',
filters: '',
@ -234,7 +234,7 @@ describe('getPingHistogram', () => {
const result = await getPingHistogram({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
from: '1234',
to: '5678',
filters: '',

View file

@ -6,7 +6,7 @@
import { getPings } from '../get_pings';
import { set } from 'lodash';
import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants';
describe('getAll', () => {
let mockEsSearchResult: any;
@ -62,7 +62,7 @@ describe('getAll', () => {
},
};
expectedGetAllParams = {
index: defaultDynamicSettings.heartbeatIndices,
index: DYNAMIC_SETTINGS_DEFAULTS.heartbeatIndices,
body: {
query: {
bool: {
@ -88,7 +88,7 @@ describe('getAll', () => {
mockEsClient.mockReturnValue(mockEsSearchResult);
const result = await getPings({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
dateRange: { from: 'now-1h', to: 'now' },
sort: 'asc',
size: 12,
@ -110,7 +110,7 @@ describe('getAll', () => {
mockEsClient.mockReturnValue(mockEsSearchResult);
await getPings({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
dateRange: { from: 'now-1h', to: 'now' },
sort: 'asc',
size: 12,
@ -166,7 +166,7 @@ describe('getAll', () => {
mockEsClient.mockReturnValue(mockEsSearchResult);
await getPings({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
dateRange: { from: 'now-1h', to: 'now' },
size: 12,
});
@ -220,7 +220,7 @@ describe('getAll', () => {
mockEsClient.mockReturnValue(mockEsSearchResult);
await getPings({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
dateRange: { from: 'now-1h', to: 'now' },
sort: 'desc',
});
@ -274,7 +274,7 @@ describe('getAll', () => {
mockEsClient.mockReturnValue(mockEsSearchResult);
await getPings({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
dateRange: { from: 'now-1h', to: 'now' },
monitorId: 'testmonitorid',
});
@ -333,7 +333,7 @@ describe('getAll', () => {
mockEsClient.mockReturnValue(mockEsSearchResult);
await getPings({
callES: mockEsClient,
dynamicSettings: defaultDynamicSettings,
dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS,
dateRange: { from: 'now-1h', to: 'now' },
status: 'down',
});

View file

@ -4,10 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
DynamicSettings,
defaultDynamicSettings,
} from '../../../../legacy/plugins/uptime/common/runtime_types';
import { DynamicSettings } from '../../../../legacy/plugins/uptime/common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../legacy/plugins/uptime/common/constants';
import { SavedObjectsType, SavedObjectsErrorHelpers } from '../../../../../src/core/server';
import { UMSavedObjectsQueryFn } from './adapters';
@ -28,12 +26,12 @@ export const umDynamicSettings: SavedObjectsType = {
heartbeatIndices: {
type: 'keyword',
},
certificatesThresholds: {
certThresholds: {
properties: {
errorState: {
expiration: {
type: 'long',
},
warningState: {
age: {
type: 'long',
},
},
@ -49,7 +47,7 @@ export const savedObjectsAdapter: UMSavedObjectsAdapter = {
return obj.attributes;
} catch (getErr) {
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
return defaultDynamicSettings;
return DYNAMIC_SETTINGS_DEFAULTS;
}
throw getErr;
}

View file

@ -5,8 +5,10 @@
*/
import expect from '@kbn/expect';
import { isRight } from 'fp-ts/lib/Either';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { defaultDynamicSettings } from '../../../../../legacy/plugins/uptime/common/runtime_types/dynamic_settings';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants';
import { DynamicSettingsType } from '../../../../../legacy/plugins/uptime/common/runtime_types';
export default function({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
@ -14,15 +16,16 @@ export default function({ getService }: FtrProviderContext) {
describe('dynamic settings', () => {
it('returns the defaults when no user settings have been saved', async () => {
const apiResponse = await supertest.get(`/api/uptime/dynamic_settings`);
expect(apiResponse.body).to.eql(defaultDynamicSettings as any);
expect(apiResponse.body).to.eql(DYNAMIC_SETTINGS_DEFAULTS);
expect(isRight(DynamicSettingsType.decode(apiResponse.body))).to.be.ok();
});
it('can change the settings', async () => {
const newSettings = {
heartbeatIndices: 'myIndex1*',
certificatesThresholds: {
errorState: 5,
warningState: 15,
certThresholds: {
expiration: 5,
age: 15,
},
};
const postResponse = await supertest
@ -35,6 +38,7 @@ export default function({ getService }: FtrProviderContext) {
const getResponse = await supertest.get(`/api/uptime/dynamic_settings`);
expect(getResponse.body).to.eql(newSettings);
expect(isRight(DynamicSettingsType.decode(getResponse.body))).to.be.ok();
});
});
}

View file

@ -6,10 +6,8 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import {
defaultDynamicSettings,
DynamicSettings,
} from '../../../../legacy/plugins/uptime/common/runtime_types';
import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../legacy/plugins/uptime/common/constants';
import { DynamicSettings } from '../../../../legacy/plugins/uptime/common/runtime_types';
import { makeChecks } from '../../../api_integration/apis/uptime/rest/helper/make_checks';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
@ -32,7 +30,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await settings.go();
const fields = await settings.loadFields();
expect(fields).to.eql(defaultDynamicSettings);
expect(fields).to.eql(DYNAMIC_SETTINGS_DEFAULTS);
});
it('should disable the apply button when invalid or unchanged', async () => {
@ -62,7 +60,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await settings.go();
const newFieldValues: DynamicSettings = { heartbeatIndices: 'new*' };
const newFieldValues: DynamicSettings = {
heartbeatIndices: 'new*',
certThresholds: {
age: 365,
expiration: 30,
},
};
await settings.changeHeartbeatIndicesInput(newFieldValues.heartbeatIndices);
await settings.apply();
@ -91,7 +95,7 @@ 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.certificatesThresholds.errorState).to.eql(newErrorThreshold);
expect(fields.certThresholds?.expiration).to.eql(newErrorThreshold);
});
it('changing certificate expiration warning threshold is reflected in settings page', async () => {
@ -108,7 +112,7 @@ 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.certificatesThresholds.warningState).to.eql(newWarningThreshold);
expect(fields.certThresholds?.age).to.eql(newWarningThreshold);
});
});
};

View file

@ -5,6 +5,7 @@
*/
import { FtrProviderContext } from '../../ftr_provider_context';
import { DynamicSettings } from '../../../../legacy/plugins/uptime/common/runtime_types';
export function UptimeSettingsProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
@ -25,24 +26,24 @@ export function UptimeSettingsProvider({ getService }: FtrProviderContext) {
await changeInputField(text, 'heartbeat-indices-input-loaded');
},
changeErrorThresholdInput: async (text: string) => {
await changeInputField(text, 'error-state-threshold-input-loaded');
await changeInputField(text, 'expiration-threshold-input-loaded');
},
changeWarningThresholdInput: async (text: string) => {
await changeInputField(text, 'warning-state-threshold-input-loaded');
await changeInputField(text, 'age-threshold-input-loaded');
},
loadFields: async () => {
loadFields: async (): Promise<DynamicSettings> => {
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 expirationInput = await testSubjects.find('expiration-threshold-input-loaded', 5000);
const ageInput = await testSubjects.find('age-threshold-input-loaded', 5000);
const heartbeatIndices = await indInput.getAttribute('value');
const errorThreshold = await errorInput.getAttribute('value');
const warningThreshold = await warningInput.getAttribute('value');
const expiration = await expirationInput.getAttribute('value');
const age = await ageInput.getAttribute('value');
return {
heartbeatIndices,
certificatesThresholds: {
errorState: errorThreshold,
warningState: warningThreshold,
certThresholds: {
age: parseInt(age, 10),
expiration: parseInt(expiration, 10),
},
};
},