From 674857e14b51d237663108d4e8882a12fc85aad5 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Apr 2020 14:23:07 -0400 Subject: [PATCH 01/49] Refactor settings form event handling and modify certs fields. --- .../plugins/uptime/common/constants/index.ts | 1 + .../common/constants/settings_defaults.ts | 15 +++++ .../common/runtime_types/dynamic_settings.ts | 14 +---- .../__tests__/certificate_form.test.tsx | 2 +- .../settings/__tests__/indices_form.test.tsx | 2 +- .../components/settings/certificate_form.tsx | 59 +++++++++++-------- .../components/settings/indices_form.tsx | 5 +- .../plugins/uptime/public/pages/settings.tsx | 55 +++++------------ .../uptime/public/pages/translations.ts | 23 ++++++++ .../public/state/reducers/dynamic_settings.ts | 27 +++++---- .../uptime/server/lib/saved_objects.ts | 12 ++-- 11 files changed, 113 insertions(+), 102 deletions(-) create mode 100644 x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts create mode 100644 x-pack/legacy/plugins/uptime/public/pages/translations.ts diff --git a/x-pack/legacy/plugins/uptime/common/constants/index.ts b/x-pack/legacy/plugins/uptime/common/constants/index.ts index 74783cf46550f..72d498056d6b3 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/index.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/index.ts @@ -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'; diff --git a/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts new file mode 100644 index 0000000000000..b77e5da983144 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts @@ -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*', + certificatesThresholds: { + expiration: 7, + age: 365, + }, +}; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts index 985b51891da99..1cd8682510d73 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts @@ -6,9 +6,9 @@ import * as t from 'io-ts'; -export const CertificatesStatesThresholdType = t.interface({ - warningState: t.number, - errorState: t.number, +export const CertificatesStatesThresholdType = t.partial({ + age: t.number, + expiration: t.number, }); export const DynamicSettingsType = t.intersection([ @@ -32,11 +32,3 @@ export const DynamicSettingsSaveType = t.intersection([ export type DynamicSettings = t.TypeOf; export type DynamicSettingsSaveResponse = t.TypeOf; export type CertificatesStatesThreshold = t.TypeOf; - -export const defaultDynamicSettings: DynamicSettings = { - heartbeatIndices: 'heartbeat-8*', - certificatesThresholds: { - errorState: 7, - warningState: 30, - }, -}; diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx index a3158f3d72445..9e8c722b72cc5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx @@ -16,7 +16,7 @@ describe('CertificateForm', () => { onChange={jest.fn()} formFields={{ heartbeatIndices: 'heartbeat-8*', - certificatesThresholds: { errorState: 7, warningState: 36 }, + certificatesThresholds: { expiration: 7, age: 36 }, }} fieldErrors={{}} isDisabled={false} diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx index 654d51019d4e5..352e30413ac6e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx @@ -16,7 +16,7 @@ describe('CertificateForm', () => { onChange={jest.fn()} formFields={{ heartbeatIndices: 'heartbeat-8*', - certificatesThresholds: { errorState: 7, warningState: 36 }, + certificatesThresholds: { expiration: 7, age: 36 }, }} fieldErrors={{}} isDisabled={false} diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx index 5103caee1e1c0..2b56dd906f3ba 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx @@ -12,18 +12,16 @@ import { EuiFormRow, EuiCode, EuiFieldNumber, + EuiText, EuiTitle, EuiSpacer, - EuiSelect, EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { defaultDynamicSettings, DynamicSettings } from '../../../common/runtime_types'; +import { DynamicSettings } from '../../../common/runtime_types'; import { selectDynamicSettings } from '../../state/selectors'; -type NumStr = string | number; - -export type OnFieldChangeType = (field: string, value?: NumStr) => void; +export type OnFieldChangeType = (changedValues: Partial) => void; export interface SettingsFormProps { onChange: OnFieldChangeType; @@ -55,15 +53,15 @@ export const CertificateExpirationForm: React.FC = ({ title={

} description={ } > @@ -76,9 +74,7 @@ export const CertificateExpirationForm: React.FC = ({ id="xpack.uptime.sourceConfiguration.errorStateDefaultValue" defaultMessage="The default value is {defaultValue}" values={{ - defaultValue: ( - {defaultDynamicSettings?.certificatesThresholds?.errorState} - ), + defaultValue: {dss.settings.certificatesThresholds?.expiration}, }} /> } @@ -86,7 +82,7 @@ export const CertificateExpirationForm: React.FC = ({ label={ } > @@ -97,17 +93,23 @@ export const CertificateExpirationForm: React.FC = ({ fullWidth disabled={isDisabled} isLoading={dss.loading} - value={formFields?.certificatesThresholds?.errorState || ''} + value={formFields?.certificatesThresholds?.expiration || ''} onChange={({ currentTarget: { value } }: any) => - onChange( - 'certificatesThresholds.errorState', - value === '' ? undefined : Number(value) - ) + onChange({ + certificatesThresholds: { + expiration: value === '' ? undefined : Number(value), + }, + }) } /> - + + + @@ -120,9 +122,7 @@ export const CertificateExpirationForm: React.FC = ({ id="xpack.uptime.sourceConfiguration.warningStateDefaultValue" defaultMessage="The default value is {defaultValue}" values={{ - defaultValue: ( - {defaultDynamicSettings?.certificatesThresholds?.warningState} - ), + defaultValue: {dss.settings.certificatesThresholds?.age}, }} /> } @@ -130,7 +130,7 @@ export const CertificateExpirationForm: React.FC = ({ label={ } > @@ -143,14 +143,21 @@ export const CertificateExpirationForm: React.FC = ({ fullWidth disabled={isDisabled} isLoading={dss.loading} - value={formFields?.certificatesThresholds?.warningState || ''} + value={formFields?.certificatesThresholds?.age || ''} onChange={(event: any) => - onChange('certificatesThresholds.warningState', Number(event.currentTarget.value)) + onChange({ + certificatesThresholds: { age: Number(event.currentTarget.value) }, + }) } /> - + + + diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx index c28eca2ea229e..c3f0342ef3ea5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx @@ -15,7 +15,6 @@ import { EuiTitle, EuiSpacer, } from '@elastic/eui'; -import { defaultDynamicSettings } from '../../../common/runtime_types'; import { selectDynamicSettings } from '../../state/selectors'; import { SettingsFormProps } from './certificate_form'; @@ -63,7 +62,7 @@ export const IndicesForm: React.FC = ({ id="xpack.uptime.sourceConfiguration.heartbeatIndicesDefaultValue" defaultMessage="The default value is {defaultValue}" values={{ - defaultValue: {defaultDynamicSettings.heartbeatIndices}, + defaultValue: {dss.settings.heartbeatIndices}, }} /> } @@ -81,7 +80,7 @@ export const IndicesForm: React.FC = ({ disabled={isDisabled} isLoading={dss.loading} value={formFields?.heartbeatIndices || ''} - onChange={(event: any) => onChange('heartbeatIndices', event.currentTarget.value)} + onChange={(event: any) => onChange({ heartbeatIndices: event.currentTarget.value })} /> diff --git a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx index 6defb96e0da3d..31f3433d3ccda 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx @@ -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, merge } from 'lodash'; import { Link } from 'react-router-dom'; import { selectDynamicSettings } from '../state/selectors'; import { getDynamicSettings, setDynamicSettings } from '../state/actions/dynamic_settings'; @@ -32,14 +31,15 @@ import { CertificateExpirationForm, OnFieldChangeType, } from '../components/settings/certificate_form'; +import * as Translations from './translations'; 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; + const errorStateErr = certificatesThresholds?.expiration ? null : blankStr; + const warningStateErr = certificatesThresholds?.age ? null : blankStr; return { heartbeatIndices: heartbeatIndErr, certificatesThresholds: @@ -57,10 +57,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,52 +67,32 @@ export const SettingsPage = () => { dispatch(getDynamicSettings()); }, [dispatch]); - const [formFields, setFormFields] = useState(dss.settings || null); - - if (!dss.loadError && formFields == null && dss.settings) { - setFormFields({ ...dss.settings }); - } + const [formFields, setFormFields] = useState({ ...dss.settings }); const fieldErrors = getFieldErrors(formFields); const isFormValid = !(fieldErrors && Object.values(fieldErrors).find(v => !!v)); - const onChangeFormField: OnFieldChangeType = (field, value) => { - if (formFields) { - const newFormFields = cloneDeep(formFields); - set(newFormFields, field, value); - setFormFields(cloneDeep(newFormFields)); - } - }; + const onChangeFormField: OnFieldChangeType = changedField => + setFormFields(Object.assign({ ...merge(formFields, changedField) })); const onApply = (event: React.FormEvent) => { event.preventDefault(); - if (formFields) { - dispatch(setDynamicSettings(formFields)); - } + dispatch(setDynamicSettings(formFields)); }; - const resetForm = () => { - if (formFields && dss.settings) { - setFormFields({ ...dss.settings }); - } - }; + const resetForm = () => setFormFields({ ...dss.settings }); - 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 : ( <> - {editNoticeText} + + {Translations.settings.editNoticeText} + ); @@ -124,9 +101,7 @@ export const SettingsPage = () => { <> - {i18n.translate('xpack.uptime.settings.returnToOverviewLinkLabel', { - defaultMessage: 'Return to overview', - })} + {Translations.settings.returnToOverviewLinkLabel} diff --git a/x-pack/legacy/plugins/uptime/public/pages/translations.ts b/x-pack/legacy/plugins/uptime/public/pages/translations.ts new file mode 100644 index 0000000000000..85e4e3f931c46 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/pages/translations.ts @@ -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', + }), +}; diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts index f003565e9873e..9d44b3b5bd1c2 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts @@ -13,15 +13,17 @@ import { setDynamicSettingsFail, } from '../actions/dynamic_settings'; import { DynamicSettings } from '../../../common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; export interface DynamicSettingsState { - settings?: DynamicSettings; + settings: DynamicSettings; loadError?: Error; saveError?: Error; loading: boolean; } const initialState: DynamicSettingsState = { + settings: DYNAMIC_SETTINGS_DEFAULTS, loading: true, }; @@ -31,23 +33,22 @@ export const dynamicSettingsReducer = handleActions( ...state, loading: true, }), - [String(getDynamicSettingsSuccess)]: (state, action: Action) => { - return { - loading: false, - settings: action.payload, - }; - }, - [String(getDynamicSettingsFail)]: (state, action: Action) => { - return { - loading: false, - loadError: action.payload, - }; - }, + [String(getDynamicSettingsSuccess)]: (state, action: Action) => ({ + ...state, + loading: false, + settings: action.payload, + }), + [String(getDynamicSettingsFail)]: (state, action: Action) => ({ + ...state, + loading: false, + loadError: action.payload, + }), [String(setDynamicSettings)]: state => ({ ...state, loading: true, }), [String(setDynamicSettingsSuccess)]: (state, action: Action) => ({ + ...state, settings: action.payload, saveSucceded: true, loading: false, diff --git a/x-pack/plugins/uptime/server/lib/saved_objects.ts b/x-pack/plugins/uptime/server/lib/saved_objects.ts index 3ccfd498c44bf..0e52207e23408 100644 --- a/x-pack/plugins/uptime/server/lib/saved_objects.ts +++ b/x-pack/plugins/uptime/server/lib/saved_objects.ts @@ -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'; @@ -30,10 +28,10 @@ export const umDynamicSettings: SavedObjectsType = { }, certificatesThresholds: { 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; } From 7d2bf4c5585ca396ab8f2363071df40c211cff40 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Apr 2020 16:10:13 -0400 Subject: [PATCH 02/49] Fix/improve broken types/unit/integration/api tests. --- .../certificate_form.test.tsx.snap | 4 ++-- .../__snapshots__/indices_form.test.tsx.snap | 4 ++-- .../components/settings/certificate_form.tsx | 8 +++----- .../state/selectors/__tests__/index.test.ts | 2 ++ .../lib/alerts/__tests__/status_check.test.ts | 12 ++++++------ .../__tests__/get_latest_monitor.test.ts | 6 +++--- .../__tests__/get_monitor_charts.test.ts | 6 +++--- .../__tests__/get_monitor_status.test.ts | 10 +++++----- .../__tests__/get_ping_histogram.test.ts | 14 +++++++------- .../lib/requests/__tests__/get_pings.test.ts | 16 ++++++++-------- .../apis/uptime/rest/dynamic_settings.ts | 12 ++++++++---- .../test/functional/apps/uptime/settings.ts | 12 +++++------- .../functional/services/uptime/settings.ts | 19 ++++++++++--------- 13 files changed, 64 insertions(+), 61 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap index 36bc9bb860211..4e1d477c9ec5a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap @@ -56,8 +56,8 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = formFields={ Object { "certificatesThresholds": Object { - "errorState": 7, - "warningState": 36, + "age": 36, + "expiration": 7, }, "heartbeatIndices": "heartbeat-8*", } diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap index 93151198c0f49..108dcf71b692f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap @@ -56,8 +56,8 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = formFields={ Object { "certificatesThresholds": Object { - "errorState": 7, - "warningState": 36, + "age": 36, + "expiration": 7, }, "heartbeatIndices": "heartbeat-8*", } diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx index 2b56dd906f3ba..76df1b7ec0a7d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx @@ -89,7 +89,7 @@ export const CertificateExpirationForm: React.FC = ({ = ({ onChange={({ currentTarget: { value } }: any) => onChange({ certificatesThresholds: { - expiration: value === '' ? undefined : Number(value), + expiration: Number(value), }, }) } @@ -137,9 +137,7 @@ export const CertificateExpirationForm: React.FC = ({ { const state: AppState = { @@ -20,6 +21,7 @@ describe('state selectors', () => { loading: false, }, dynamicSettings: { + settings: DYNAMIC_SETTINGS_DEFAULTS, loading: false, }, monitor: { diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 2cc6f23ebaae5..fe14986e0d2c6 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -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, @@ -89,8 +89,8 @@ describe('status check alert', () => { "callES": [MockFunction], "dynamicSettings": Object { "certificatesThresholds": Object { - "errorState": 7, - "warningState": 30, + "age": 365, + "expiration": 7, }, "heartbeatIndices": "heartbeat-8*", }, @@ -136,8 +136,8 @@ describe('status check alert', () => { "callES": [MockFunction], "dynamicSettings": Object { "certificatesThresholds": Object { - "errorState": 7, - "warningState": 30, + "age": 365, + "expiration": 7, }, "heartbeatIndices": "heartbeat-8*", }, diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts index cf8414a3b0a68..03e2bc7a44bd0 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts @@ -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', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts index c740581734fdd..5d3f9ce8d4ad9 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts @@ -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', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts index e429de9ae0d68..1c26cab4b60ff 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts @@ -7,7 +7,7 @@ import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; import { getMonitorStatus } from '../get_monitor_status'; import { ScopedClusterClient } from 'src/core/server/elasticsearch'; -import { defaultDynamicSettings } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../legacy/plugins/uptime/common/constants'; 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: { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts index faeb291bb533b..4de7d3ffd2a7d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts @@ -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: '', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts index fcf773db23de6..abd3655cc6402 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts @@ -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', }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts index a1b731169f0a0..e7bbe5baa2ad0 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts @@ -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, + 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(); }); }); } diff --git a/x-pack/test/functional/apps/uptime/settings.ts b/x-pack/test/functional/apps/uptime/settings.ts index e81bbc5ae42f9..8f7aab7aa6e29 100644 --- a/x-pack/test/functional/apps/uptime/settings.ts +++ b/x-pack/test/functional/apps/uptime/settings.ts @@ -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 () => { @@ -91,7 +89,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.certificatesThresholds?.expiration).to.eql(newErrorThreshold); }); it('changing certificate expiration warning threshold is reflected in settings page', async () => { @@ -108,7 +106,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.certificatesThresholds?.age).to.eql(newWarningThreshold); }); }); }; diff --git a/x-pack/test/functional/services/uptime/settings.ts b/x-pack/test/functional/services/uptime/settings.ts index 14cab368b766a..4700a3585c8a6 100644 --- a/x-pack/test/functional/services/uptime/settings.ts +++ b/x-pack/test/functional/services/uptime/settings.ts @@ -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 => { 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 errorInput = await testSubjects.find('expiration-threshold-input-loaded', 5000); + const warningInput = 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 errorInput.getAttribute('value'); + const age = await warningInput.getAttribute('value'); return { heartbeatIndices, certificatesThresholds: { - errorState: errorThreshold, - warningState: warningThreshold, + age: parseInt(age, 10), + expiration: parseInt(expiration, 10), }, }; }, From d0246a72202ca58952fefdec026b7d5e2106b5f3 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 10:28:28 -0400 Subject: [PATCH 03/49] Modify default expiration threshold. --- .../legacy/plugins/uptime/common/constants/settings_defaults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts index b77e5da983144..f7eee27ba438d 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts @@ -9,7 +9,7 @@ import { DynamicSettings } from '../runtime_types'; export const DYNAMIC_SETTINGS_DEFAULTS: DynamicSettings = { heartbeatIndices: 'heartbeat-8*', certificatesThresholds: { - expiration: 7, + expiration: 30, age: 365, }, }; From f7678b685567f28ccb34c78f6010710b17c1b4b7 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 10:39:15 -0400 Subject: [PATCH 04/49] Rename test vars. --- x-pack/test/functional/services/uptime/settings.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/services/uptime/settings.ts b/x-pack/test/functional/services/uptime/settings.ts index 4700a3585c8a6..1eb19d2993e28 100644 --- a/x-pack/test/functional/services/uptime/settings.ts +++ b/x-pack/test/functional/services/uptime/settings.ts @@ -33,11 +33,11 @@ export function UptimeSettingsProvider({ getService }: FtrProviderContext) { }, loadFields: async (): Promise => { const indInput = await testSubjects.find('heartbeat-indices-input-loaded', 5000); - const errorInput = await testSubjects.find('expiration-threshold-input-loaded', 5000); - const warningInput = await testSubjects.find('age-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 expiration = await errorInput.getAttribute('value'); - const age = await warningInput.getAttribute('value'); + const expiration = await expirationInput.getAttribute('value'); + const age = await ageInput.getAttribute('value'); return { heartbeatIndices, From 0ddd97b6031717ab5176f3fa5c9d8cfa4e435529 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 12:15:49 -0400 Subject: [PATCH 05/49] Implement PR feedback. --- .../common/constants/settings_defaults.ts | 2 +- .../common/runtime_types/dynamic_settings.ts | 16 +- .../__tests__/certificate_form.test.tsx | 3 +- .../settings/__tests__/indices_form.test.tsx | 3 +- .../components/settings/certificate_form.tsx | 246 +++++++++--------- .../components/settings/indices_form.tsx | 112 ++++---- .../plugins/uptime/public/pages/settings.tsx | 34 ++- .../public/state/reducers/dynamic_settings.ts | 5 +- .../uptime/public/state/selectors/index.ts | 4 +- .../uptime/server/lib/saved_objects.ts | 2 +- .../test/functional/apps/uptime/settings.ts | 12 +- .../functional/services/uptime/settings.ts | 2 +- 12 files changed, 230 insertions(+), 211 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts index f7eee27ba438d..b7986679a09ca 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/settings_defaults.ts @@ -8,7 +8,7 @@ import { DynamicSettings } from '../runtime_types'; export const DYNAMIC_SETTINGS_DEFAULTS: DynamicSettings = { heartbeatIndices: 'heartbeat-8*', - certificatesThresholds: { + certThresholds: { expiration: 30, age: 365, }, diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts index 1cd8682510d73..da887cc5055c1 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/dynamic_settings.ts @@ -6,19 +6,15 @@ import * as t from 'io-ts'; -export const CertificatesStatesThresholdType = t.partial({ +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,4 +27,4 @@ export const DynamicSettingsSaveType = t.intersection([ export type DynamicSettings = t.TypeOf; export type DynamicSettingsSaveResponse = t.TypeOf; -export type CertificatesStatesThreshold = t.TypeOf; +export type CertStateThresholds = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx index 9e8c722b72cc5..b396a92828d22 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx @@ -13,10 +13,11 @@ describe('CertificateForm', () => { expect( shallowWithRouter( { expect( shallowWithRouter( ) => void; +interface ChangedValues { + heartbeatIndices?: string; + certThresholds?: Partial; +} + +export type OnFieldChangeType = (changedValues: ChangedValues) => void; export interface SettingsFormProps { + loading: boolean; onChange: OnFieldChangeType; formFields: DynamicSettings | null; fieldErrors: any; @@ -31,135 +36,134 @@ export interface SettingsFormProps { } export const CertificateExpirationForm: React.FC = ({ + loading, onChange, formFields, fieldErrors, isDisabled, -}) => { - const dss = useSelector(selectDynamicSettings); - - return ( - <> - -

+}) => ( + <> + +

+ +

+
+ + +

+ } + description={ + + } + > + {DYNAMIC_SETTINGS_DEFAULTS.certThresholds.expiration} + ), + }} /> - -
- - - - } - description={ + isInvalid={!!fieldErrors?.certificatesThresholds?.errorState} + label={ } > - {dss.settings.certificatesThresholds?.expiration}, - }} + + + + onChange({ + certThresholds: { + expiration: Number(e.target.value), + }, + }) + } /> - } - isInvalid={!!fieldErrors?.certificatesThresholds?.errorState} - label={ - - } - > - - - - onChange({ - certificatesThresholds: { - expiration: Number(value), - }, - }) - } + + + + - - - - - - - - - {dss.settings.certificatesThresholds?.age}, - }} - /> - } - isInvalid={!!fieldErrors?.certificatesThresholds?.warningState} - label={ - +
+
+ + {DYNAMIC_SETTINGS_DEFAULTS.certThresholds.age}, + }} + /> + } + isInvalid={!!fieldErrors?.certificatesThresholds?.warningState} + label={ + + } + > + + + + onChange({ + certThresholds: { age: Number(e.currentTarget.value) }, + }) + } /> - } - > - - - - onChange({ - certificatesThresholds: { age: Number(event.currentTarget.value) }, - }) - } + + + + - - - - - - - - - - - ); -}; + +
+
+ + + +); diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx index c3f0342ef3ea5..23c93da8896e2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/indices_form.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useSelector } from 'react-redux'; import { EuiDescribedFormGroup, EuiFormRow, @@ -15,75 +14,72 @@ import { EuiTitle, EuiSpacer, } from '@elastic/eui'; -import { selectDynamicSettings } from '../../state/selectors'; import { SettingsFormProps } from './certificate_form'; +import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; export const IndicesForm: React.FC = ({ onChange, + loading, formFields, fieldErrors, isDisabled, -}) => { - const dss = useSelector(selectDynamicSettings); - - return ( - <> - -

+}) => ( + <> + +

+ +

+
+ + + +

+ } + description={ + + } + > + {DYNAMIC_SETTINGS_DEFAULTS.heartbeatIndices}, + }} /> - -
- - - - } - description={ + isInvalid={!!fieldErrors?.heartbeatIndices} + label={ } > - {dss.settings.heartbeatIndices}, - }} - /> - } - isInvalid={!!fieldErrors?.heartbeatIndices} - label={ - - } - > - onChange({ heartbeatIndices: event.currentTarget.value })} - /> - - - - ); -}; + disabled={isDisabled} + isLoading={loading} + value={formFields?.heartbeatIndices || ''} + onChange={(event: any) => onChange({ heartbeatIndices: event.currentTarget.value })} + /> + + + +); diff --git a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx index 31f3433d3ccda..58e6b83d334fd 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch, useSelector } from 'react-redux'; -import { isEqual, merge } from 'lodash'; +import { isEqual } from 'lodash'; import { Link } from 'react-router-dom'; import { selectDynamicSettings } from '../state/selectors'; import { getDynamicSettings, setDynamicSettings } from '../state/actions/dynamic_settings'; @@ -36,7 +36,7 @@ import * as Translations from './translations'; const getFieldErrors = (formFields: DynamicSettings | 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?.expiration ? null : blankStr; const warningStateErr = certificatesThresholds?.age ? null : blankStr; @@ -67,21 +67,39 @@ export const SettingsPage = () => { dispatch(getDynamicSettings()); }, [dispatch]); - const [formFields, setFormFields] = useState({ ...dss.settings }); + const [formFields, setFormFields] = useState( + dss.settings ? { ...dss.settings } : null + ); + + 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 = changedField => - setFormFields(Object.assign({ ...merge(formFields, changedField) })); + const onChangeFormField: OnFieldChangeType = changedField => { + if (formFields) { + setFormFields({ + heartbeatIndices: changedField.heartbeatIndices ?? formFields.heartbeatIndices, + certThresholds: Object.assign( + {}, + formFields.certThresholds, + changedField?.certThresholds ?? null + ), + }); + } + }; const onApply = (event: React.FormEvent) => { event.preventDefault(); - dispatch(setDynamicSettings(formFields)); + if (formFields) { + dispatch(setDynamicSettings(formFields)); + } }; - const resetForm = () => setFormFields({ ...dss.settings }); + const resetForm = () => setFormFields(dss.settings ? { ...dss.settings } : null); const isFormDirty = !isEqual(dss.settings, formFields); const canEdit: boolean = @@ -114,12 +132,14 @@ export const SettingsPage = () => {
( loading: true, }), [String(setDynamicSettingsSuccess)]: (state, action: Action) => ({ - ...state, settings: action.payload, saveSucceded: true, loading: false, diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 7260c61f44147..15fc8b8a7b173 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -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 }; diff --git a/x-pack/plugins/uptime/server/lib/saved_objects.ts b/x-pack/plugins/uptime/server/lib/saved_objects.ts index 0e52207e23408..d849fbd8ce0a8 100644 --- a/x-pack/plugins/uptime/server/lib/saved_objects.ts +++ b/x-pack/plugins/uptime/server/lib/saved_objects.ts @@ -26,7 +26,7 @@ export const umDynamicSettings: SavedObjectsType = { heartbeatIndices: { type: 'keyword', }, - certificatesThresholds: { + certThresholds: { properties: { expiration: { type: 'long', diff --git a/x-pack/test/functional/apps/uptime/settings.ts b/x-pack/test/functional/apps/uptime/settings.ts index 8f7aab7aa6e29..64b6300e0df63 100644 --- a/x-pack/test/functional/apps/uptime/settings.ts +++ b/x-pack/test/functional/apps/uptime/settings.ts @@ -60,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(); @@ -89,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?.expiration).to.eql(newErrorThreshold); + expect(fields.certThresholds?.expiration).to.eql(newErrorThreshold); }); it('changing certificate expiration warning threshold is reflected in settings page', async () => { @@ -106,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?.age).to.eql(newWarningThreshold); + expect(fields.certThresholds?.age).to.eql(newWarningThreshold); }); }); }; diff --git a/x-pack/test/functional/services/uptime/settings.ts b/x-pack/test/functional/services/uptime/settings.ts index 1eb19d2993e28..9719152b62d35 100644 --- a/x-pack/test/functional/services/uptime/settings.ts +++ b/x-pack/test/functional/services/uptime/settings.ts @@ -41,7 +41,7 @@ export function UptimeSettingsProvider({ getService }: FtrProviderContext) { return { heartbeatIndices, - certificatesThresholds: { + certThresholds: { age: parseInt(age, 10), expiration: parseInt(expiration, 10), }, From ef2128680c1aef190873433730c37d808fbf74f4 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 12:59:34 -0400 Subject: [PATCH 06/49] Refresh snapshots, fix broken tests/types. --- .../__snapshots__/certificate_form.test.tsx.snap | 3 ++- .../__tests__/__snapshots__/indices_form.test.tsx.snap | 3 ++- .../server/lib/alerts/__tests__/status_check.test.ts | 8 ++++---- .../server/lib/requests/__tests__/get_certs.test.ts | 6 +++++- .../api_integration/apis/uptime/rest/dynamic_settings.ts | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap index 4e1d477c9ec5a..a0fab80ace84e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap @@ -55,7 +55,7 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = fieldErrors={Object {}} formFields={ Object { - "certificatesThresholds": Object { + "certThresholds": Object { "age": 36, "expiration": 7, }, @@ -63,6 +63,7 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = } } isDisabled={false} + loading={false} onChange={[MockFunction]} /> diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap index 108dcf71b692f..6b9ce5a4b6947 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/indices_form.test.tsx.snap @@ -55,7 +55,7 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = fieldErrors={Object {}} formFields={ Object { - "certificatesThresholds": Object { + "certThresholds": Object { "age": 36, "expiration": 7, }, @@ -63,6 +63,7 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = } } isDisabled={false} + loading={false} onChange={[MockFunction]} /> diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index fe14986e0d2c6..4f4c6e3011ad1 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -88,9 +88,9 @@ describe('status check alert', () => { Object { "callES": [MockFunction], "dynamicSettings": Object { - "certificatesThresholds": Object { + "certThresholds": Object { "age": 365, - "expiration": 7, + "expiration": 30, }, "heartbeatIndices": "heartbeat-8*", }, @@ -135,9 +135,9 @@ describe('status check alert', () => { Object { "callES": [MockFunction], "dynamicSettings": Object { - "certificatesThresholds": Object { + "certThresholds": Object { "age": 365, - "expiration": 7, + "expiration": 30, }, "heartbeatIndices": "heartbeat-8*", }, diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts index 539344dfca791..b49a6b22ff976 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts @@ -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', diff --git a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts index e7bbe5baa2ad0..ea980721b831b 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/dynamic_settings.ts @@ -23,7 +23,7 @@ export default function({ getService }: FtrProviderContext) { it('can change the settings', async () => { const newSettings = { heartbeatIndices: 'myIndex1*', - certificatesThresholds: { + certThresholds: { expiration: 5, age: 15, }, From 84129513f7463ec7dddf2b78be245291f3bb8517 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 13:08:46 -0400 Subject: [PATCH 07/49] Remove unnecessary state spreading. --- .../uptime/public/state/reducers/dynamic_settings.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts index 624bb8a51c481..a9ad58e64e552 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/dynamic_settings.ts @@ -31,13 +31,11 @@ export const dynamicSettingsReducer = handleActions( ...state, loading: true, }), - [String(getDynamicSettingsSuccess)]: (state, action: Action) => ({ - ...state, + [String(getDynamicSettingsSuccess)]: (_state, action: Action) => ({ loading: false, settings: action.payload, }), - [String(getDynamicSettingsFail)]: (state, action: Action) => ({ - ...state, + [String(getDynamicSettingsFail)]: (_state, action: Action) => ({ loading: false, loadError: action.payload, }), @@ -45,7 +43,7 @@ export const dynamicSettingsReducer = handleActions( ...state, loading: true, }), - [String(setDynamicSettingsSuccess)]: (state, action: Action) => ({ + [String(setDynamicSettingsSuccess)]: (_state, action: Action) => ({ settings: action.payload, saveSucceded: true, loading: false, From 59e60e8b5b830f0fc21fd793cd75137f9675124e Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 13:26:58 -0400 Subject: [PATCH 08/49] Add type for settings field errors. --- .../__tests__/certificate_form.test.tsx | 2 +- .../settings/__tests__/indices_form.test.tsx | 2 +- .../components/settings/certificate_form.tsx | 19 ++++--------- .../components/settings/indices_form.tsx | 2 +- .../plugins/uptime/public/pages/settings.tsx | 28 +++++++++++++++---- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx index b396a92828d22..3d4bd58aabe0f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx @@ -19,7 +19,7 @@ describe('CertificateForm', () => { heartbeatIndices: 'heartbeat-8*', certThresholds: { expiration: 7, age: 36 }, }} - fieldErrors={{}} + fieldErrors={null} isDisabled={false} /> ) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx index 5c635250d19e6..07a3bf81e39d8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/indices_form.test.tsx @@ -19,7 +19,7 @@ describe('CertificateForm', () => { heartbeatIndices: 'heartbeat-8*', certThresholds: { expiration: 7, age: 36 }, }} - fieldErrors={{}} + fieldErrors={null} isDisabled={false} /> ) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx index db74b91faa255..1927e4389e692 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx @@ -17,8 +17,9 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { DynamicSettings, CertStateThresholds } from '../../../common/runtime_types'; +import { CertStateThresholds } from '../../../common/runtime_types'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; +import { SettingsFormProps } from '../../pages/settings'; interface ChangedValues { heartbeatIndices?: string; @@ -27,14 +28,6 @@ interface ChangedValues { export type OnFieldChangeType = (changedValues: ChangedValues) => void; -export interface SettingsFormProps { - loading: boolean; - onChange: OnFieldChangeType; - formFields: DynamicSettings | null; - fieldErrors: any; - isDisabled: boolean; -} - export const CertificateExpirationForm: React.FC = ({ loading, onChange, @@ -70,7 +63,7 @@ export const CertificateExpirationForm: React.FC = ({ > = ({ }} /> } - isInvalid={!!fieldErrors?.certificatesThresholds?.errorState} + isInvalid={!!fieldErrors?.certificatesThresholds?.expirationThresholdError} label={ = ({ = ({ }} /> } - isInvalid={!!fieldErrors?.certificatesThresholds?.warningState} + isInvalid={!!fieldErrors?.certificatesThresholds?.ageThresholdError} label={ = ({ onChange, diff --git a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx index 58e6b83d334fd..d8c2a78092854 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/settings.tsx @@ -33,20 +33,36 @@ import { } 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 { certThresholds: certificatesThresholds, heartbeatIndices } = formFields; const heartbeatIndErr = heartbeatIndices.match(/^\S+$/) ? '' : blankStr; - const errorStateErr = certificatesThresholds?.expiration ? null : blankStr; - const warningStateErr = certificatesThresholds?.age ? 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, }; From d47d4295de5f67008a6c7106460b74bc84b71b75 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 13:30:08 -0400 Subject: [PATCH 09/49] Refresh test snapshots. --- .../__tests__/__snapshots__/certificate_form.test.tsx.snap | 2 +- .../settings/__tests__/__snapshots__/indices_form.test.tsx.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap index a0fab80ace84e..96d472c91680d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/settings/__tests__/__snapshots__/certificate_form.test.tsx.snap @@ -52,7 +52,7 @@ exports[`CertificateForm shallow renders expected elements for valid props 1`] = } > Date: Thu, 23 Apr 2020 13:34:02 -0400 Subject: [PATCH 10/49] Improve punctuation. --- .../uptime/public/components/settings/certificate_form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx index 1927e4389e692..209e38785e165 100644 --- a/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/settings/certificate_form.tsx @@ -57,7 +57,7 @@ export const CertificateExpirationForm: React.FC = ({ description={ } > From 929e38a3dcf9409b6234248132723f7a1c145856 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 16 Apr 2020 16:54:06 -0400 Subject: [PATCH 11/49] Add TLS alert type. --- .../plugins/uptime/server/lib/alerts/tls.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 x-pack/plugins/uptime/server/lib/alerts/tls.ts diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts new file mode 100644 index 0000000000000..7dfb77a6d88bb --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -0,0 +1,28 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { UptimeAlertTypeFactory } from './types'; + +export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ + id: 'xpack.uptime.alerts.tls', + name: i18n.translate('xpack/uptime.alerts.tls', { + defaultMessage: 'Uptime TLS', + }), + validate: { + params: schema.object({ + sha256: schema.string(), + }), + }, + defaultActionGroupId: 'TODO-ADD-HERE', + actionGroups: [], + actionVariables: { + context: [], + state: [], + }, + async executor(options) {}, +}); From 07055cb960b912e0db3a223d5e177dad1e585c86 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 8 Apr 2020 17:16:40 -0400 Subject: [PATCH 12/49] Add cert API request and runtime type checking. --- x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index 84154429b9188..f585a3182fb04 100644 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { GetCertsParams } from './get_certs'; import { UMElasticsearchQueryFn } from '../adapters'; import { HistogramResult, From 9e017cba93ce2a41bce4fe4d2aebafc0c9bd98a5 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 16 Apr 2020 15:51:46 -0400 Subject: [PATCH 13/49] Add api test for cert api. --- x-pack/test/api_integration/apis/uptime/rest/certs.ts | 1 - x-pack/test/api_integration/apis/uptime/rest/index.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/uptime/rest/certs.ts b/x-pack/test/api_integration/apis/uptime/rest/certs.ts index 7510ea3f34d28..1c1d951df78ca 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/certs.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/certs.ts @@ -24,7 +24,6 @@ export default function({ getService }: FtrProviderContext) { expect(JSON.stringify(apiResponse.body)).to.eql('{"certs":[]}'); }); }); - describe('when data is present', async () => { const now = moment(); const cnva = now.add(6, 'months').toISOString(); diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index f77c14e960ab2..0f7129ee6a0d7 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -44,6 +44,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dynamic_settings')); loadTestFile(require.resolve('./snapshot')); loadTestFile(require.resolve('./monitor_states_generated')); + loadTestFile(require.resolve('./snapshot')); loadTestFile(require.resolve('./telemetry_collectors')); }); describe('with real-world data', () => { From b7ab73bde32e624594da1843b240b4e5b3609dc7 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 16 Apr 2020 16:24:59 -0400 Subject: [PATCH 14/49] Add unload command to certs test. --- x-pack/test/api_integration/apis/uptime/rest/certs.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/api_integration/apis/uptime/rest/certs.ts b/x-pack/test/api_integration/apis/uptime/rest/certs.ts index 1c1d951df78ca..7510ea3f34d28 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/certs.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/certs.ts @@ -24,6 +24,7 @@ export default function({ getService }: FtrProviderContext) { expect(JSON.stringify(apiResponse.body)).to.eql('{"certs":[]}'); }); }); + describe('when data is present', async () => { const now = moment(); const cnva = now.add(6, 'months').toISOString(); From 6f007bc5e3f68051caf40b834befd2d6dd4b020a Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 17 Apr 2020 10:05:29 -0400 Subject: [PATCH 15/49] Extract API params interface to io-ts type. --- x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index f585a3182fb04..84154429b9188 100644 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GetCertsParams } from './get_certs'; import { UMElasticsearchQueryFn } from '../adapters'; import { HistogramResult, From a18ee01bb1aa6624564ac885e6f6dc16c658b2f1 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 17 Apr 2020 17:21:24 -0400 Subject: [PATCH 16/49] Add TLS alert type on server. --- .../plugins/uptime/common/constants/alerts.ts | 4 ++ .../common/runtime_types/alerts/common.ts | 23 +++++++ .../common/runtime_types/alerts/index.ts | 8 +-- .../runtime_types/alerts/status_check.ts | 16 ----- .../uptime/common/runtime_types/certs.ts | 2 + .../uptime/server/lib/alerts/common.ts | 60 +++++++++++++++++++ .../plugins/uptime/server/lib/alerts/index.ts | 6 +- .../uptime/server/lib/alerts/status_check.ts | 56 +---------------- .../plugins/uptime/server/lib/alerts/tls.ts | 48 +++++++++++++-- .../uptime/server/lib/requests/get_certs.ts | 24 +++++++- 10 files changed, 166 insertions(+), 81 deletions(-) create mode 100644 x-pack/legacy/plugins/uptime/common/runtime_types/alerts/common.ts create mode 100644 x-pack/plugins/uptime/server/lib/alerts/common.ts diff --git a/x-pack/legacy/plugins/uptime/common/constants/alerts.ts b/x-pack/legacy/plugins/uptime/common/constants/alerts.ts index c0db9ae309843..eea9e07714454 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/alerts.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/alerts.ts @@ -16,4 +16,8 @@ export const ACTION_GROUP_DEFINITIONS: ActionGroupDefinitions = { id: 'xpack.uptime.alerts.actionGroups.monitorStatus', name: 'Uptime Down Monitor', }, + TLS: { + id: 'xpack.uptime.alerts.actionGroups.tls', + name: 'Uptime TLS Alert', + }, }; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/common.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/common.ts new file mode 100644 index 0000000000000..a6b03551c9061 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/common.ts @@ -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 * as t from 'io-ts'; + +export const UptimeCommonStateType = t.intersection([ + t.partial({ + currentTriggerStarted: t.string, + firstTriggeredAt: t.string, + lastTriggeredAt: t.string, + lastResolvedAt: t.string, + }), + t.type({ + firstCheckedAt: t.string, + lastCheckedAt: t.string, + isTriggered: t.boolean, + }), +]); + +export type UptimeCommonState = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/index.ts index ee284249c38c0..9b2138fc2524c 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/index.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/index.ts @@ -4,9 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - StatusCheckAlertStateType, - StatusCheckAlertState, - StatusCheckExecutorParamsType, - StatusCheckExecutorParams, -} from './status_check'; +export * from './common'; +export * from './status_check'; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/status_check.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/status_check.ts index bc234b268df27..909669bb5d3eb 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/status_check.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/alerts/status_check.ts @@ -6,22 +6,6 @@ import * as t from 'io-ts'; -export const StatusCheckAlertStateType = t.intersection([ - t.partial({ - currentTriggerStarted: t.string, - firstTriggeredAt: t.string, - lastTriggeredAt: t.string, - lastResolvedAt: t.string, - }), - t.type({ - firstCheckedAt: t.string, - lastCheckedAt: t.string, - isTriggered: t.boolean, - }), -]); - -export type StatusCheckAlertState = t.TypeOf; - export const StatusCheckExecutorParamsType = t.intersection([ t.partial({ filters: t.string, diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/certs.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/certs.ts index e8be67abf3a44..790e4fd79afe3 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/certs.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/certs.ts @@ -15,6 +15,8 @@ export const GetCertsParamsType = t.intersection([ }), t.partial({ search: t.string, + notValidBefore: t.string, + notValidAfter: t.string, }), ]); diff --git a/x-pack/plugins/uptime/server/lib/alerts/common.ts b/x-pack/plugins/uptime/server/lib/alerts/common.ts new file mode 100644 index 0000000000000..3bd40b0b6cd9d --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/common.ts @@ -0,0 +1,60 @@ +/* + * 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 { isRight } from 'fp-ts/lib/Either'; +import { + UptimeCommonState, + UptimeCommonStateType, +} from '../../../../../legacy/plugins/uptime/common/runtime_types'; + +export type UpdateUptimeAlertState = ( + state: Record, + isTriggeredNow: boolean +) => UptimeCommonState; + +export const updateState: UpdateUptimeAlertState = (state, isTriggeredNow) => { + const now = new Date().toISOString(); + const decoded = UptimeCommonStateType.decode(state); + if (!isRight(decoded)) { + const triggerVal = isTriggeredNow ? now : undefined; + return { + currentTriggerStarted: triggerVal, + firstCheckedAt: now, + firstTriggeredAt: triggerVal, + isTriggered: isTriggeredNow, + lastTriggeredAt: triggerVal, + lastCheckedAt: now, + lastResolvedAt: undefined, + }; + } + const { + currentTriggerStarted, + firstCheckedAt, + firstTriggeredAt, + lastTriggeredAt, + // this is the stale trigger status, we're naming it `wasTriggered` + // to differentiate it from the `isTriggeredNow` param + isTriggered: wasTriggered, + lastResolvedAt, + } = decoded.right; + + let cts: string | undefined; + if (isTriggeredNow && !currentTriggerStarted) { + cts = now; + } else if (isTriggeredNow) { + cts = currentTriggerStarted; + } + + return { + currentTriggerStarted: cts, + firstCheckedAt: firstCheckedAt ?? now, + firstTriggeredAt: isTriggeredNow && !firstTriggeredAt ? now : firstTriggeredAt, + lastCheckedAt: now, + lastTriggeredAt: isTriggeredNow ? now : lastTriggeredAt, + lastResolvedAt: !isTriggeredNow && wasTriggered ? now : lastResolvedAt, + isTriggered: isTriggeredNow, + }; +}; diff --git a/x-pack/plugins/uptime/server/lib/alerts/index.ts b/x-pack/plugins/uptime/server/lib/alerts/index.ts index 0e61fd70e0024..661df39ece628 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/index.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/index.ts @@ -6,5 +6,9 @@ import { UptimeAlertTypeFactory } from './types'; import { statusCheckAlertFactory } from './status_check'; +import { tlsAlertFactory } from './tls'; -export const uptimeAlertTypeFactories: UptimeAlertTypeFactory[] = [statusCheckAlertFactory]; +export const uptimeAlertTypeFactories: UptimeAlertTypeFactory[] = [ + statusCheckAlertFactory, + tlsAlertFactory, +]; diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 829e6f92d3702..f6711dfc5144f 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -12,12 +12,9 @@ import { AlertExecutorOptions } from '../../../../alerting/server'; import { ACTION_GROUP_DEFINITIONS } from '../../../../../legacy/plugins/uptime/common/constants'; import { UptimeAlertTypeFactory } from './types'; import { GetMonitorStatusResult } from '../requests'; -import { - StatusCheckExecutorParamsType, - StatusCheckAlertStateType, - StatusCheckAlertState, -} from '../../../../../legacy/plugins/uptime/common/runtime_types'; +import { StatusCheckExecutorParamsType } from '../../../../../legacy/plugins/uptime/common/runtime_types'; import { savedObjectsAdapter } from '../saved_objects'; +import { updateState } from './common'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -122,58 +119,11 @@ export const fullListByIdAndLocation = ( ); }; -export const updateState = ( - state: Record, - isTriggeredNow: boolean -): StatusCheckAlertState => { - const now = new Date().toISOString(); - const decoded = StatusCheckAlertStateType.decode(state); - if (!isRight(decoded)) { - const triggerVal = isTriggeredNow ? now : undefined; - return { - currentTriggerStarted: triggerVal, - firstCheckedAt: now, - firstTriggeredAt: triggerVal, - isTriggered: isTriggeredNow, - lastTriggeredAt: triggerVal, - lastCheckedAt: now, - lastResolvedAt: undefined, - }; - } - const { - currentTriggerStarted, - firstCheckedAt, - firstTriggeredAt, - lastTriggeredAt, - // this is the stale trigger status, we're naming it `wasTriggered` - // to differentiate it from the `isTriggeredNow` param - isTriggered: wasTriggered, - lastResolvedAt, - } = decoded.right; - - let cts: string | undefined; - if (isTriggeredNow && !currentTriggerStarted) { - cts = now; - } else if (isTriggeredNow) { - cts = currentTriggerStarted; - } - - return { - currentTriggerStarted: cts, - firstCheckedAt: firstCheckedAt ?? now, - firstTriggeredAt: isTriggeredNow && !firstTriggeredAt ? now : firstTriggeredAt, - lastCheckedAt: now, - lastTriggeredAt: isTriggeredNow ? now : lastTriggeredAt, - lastResolvedAt: !isTriggeredNow && wasTriggered ? now : lastResolvedAt, - isTriggered: isTriggeredNow, - }; -}; - // Right now the maximum number of monitors shown in the message is hardcoded here. // we might want to make this a parameter in the future const DEFAULT_MAX_MESSAGE_ROWS = 3; -export const statusCheckAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ +export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.monitorStatus', name: i18n.translate('xpack.uptime.alerts.monitorStatus', { defaultMessage: 'Uptime monitor status', diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 7dfb77a6d88bb..89211c70831e8 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -7,6 +7,11 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { UptimeAlertTypeFactory } from './types'; +import { savedObjectsAdapter } from '../saved_objects'; +import { updateState } from './common'; +import { ACTION_GROUP_DEFINITIONS } from '../../../../../legacy/plugins/uptime/common/constants'; + +const { TLS } = ACTION_GROUP_DEFINITIONS; export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ id: 'xpack.uptime.alerts.tls', @@ -15,14 +20,49 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ }), validate: { params: schema.object({ - sha256: schema.string(), + from: schema.string(), + to: schema.string(), + index: schema.number(), + size: schema.number(), + search: schema.maybe(schema.string()), + filters: schema.maybe(schema.string()), }), }, - defaultActionGroupId: 'TODO-ADD-HERE', - actionGroups: [], + defaultActionGroupId: TLS.id, + actionGroups: [ + { + id: TLS.id, + name: TLS.name, + }, + ], actionVariables: { context: [], state: [], }, - async executor(options) {}, + async executor(options) { + const { + params: { from, to, index, size, search }, + services: { alertInstanceFactory, callCluster, savedObjectsClient }, + state, + } = options; + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); + const certs = await libs.requests.getCerts({ + callES: callCluster, + dynamicSettings, + from, + to, + index, + size, + search, + }); + + if (certs.length) { + const alertInstance = alertInstanceFactory(TLS.id); + // this is not scalable + alertInstance.replaceState({ certs }); + alertInstance.scheduleActions(TLS.id); + } + + return updateState(state, certs.length > 0); + }, }); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 4f99fbe94d54c..6bd85834fe237 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -13,8 +13,10 @@ export const getCerts: UMElasticsearchQueryFn = async ({ index, from, to, - search, size, + search, + notValidBefore, + notValidAfter, }) => { const searchWrapper = `*${search}*`; const params: any = { @@ -100,6 +102,26 @@ export const getCerts: UMElasticsearchQueryFn = async ({ ]; } + if (notValidBefore) { + params.body.query.bool.filter.push({ + range: { + 'tls.certificate_not_valid_before': { + lte: notValidBefore, + }, + }, + }); + } + + if (notValidAfter) { + params.body.query.bool.filter.push({ + range: { + 'tls.certificate_not_valid_after': { + lte: notValidAfter, + }, + }, + }); + } + const result = await callES('search', params); const formatted = (result?.hits?.hits ?? []).map((hit: any) => { const { From ff773bff16d467ca2df639eebefc15842bc86f98 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 17 Apr 2020 17:56:11 -0400 Subject: [PATCH 17/49] WIP - add state for changing selected alert type. --- .../plugins/uptime/common/constants/alerts.ts | 5 ++++ .../uptime_alerts_flyout_wrapper.tsx | 8 +++--- .../overview/overview_container.tsx | 27 +++++++++++-------- .../plugins/uptime/public/pages/overview.tsx | 18 ++++++++----- .../plugins/uptime/public/state/actions/ui.ts | 2 ++ .../uptime/public/state/reducers/ui.ts | 10 +++++++ .../uptime/public/state/selectors/index.ts | 2 ++ .../plugins/uptime/public/uptime_app.tsx | 5 +--- 8 files changed, 52 insertions(+), 25 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/common/constants/alerts.ts b/x-pack/legacy/plugins/uptime/common/constants/alerts.ts index eea9e07714454..a259fc0a3eb81 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/alerts.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/alerts.ts @@ -21,3 +21,8 @@ export const ACTION_GROUP_DEFINITIONS: ActionGroupDefinitions = { name: 'Uptime TLS Alert', }, }; + +export const CLIENT_ALERT_TYPES = { + MONITOR_STATUS: 'xpack.uptime.alerts.monitorStatus', + TLS: 'xpack.uptime.alerts.tls', +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx index 7bfd44a762455..49f2954c8e7d6 100644 --- a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx @@ -7,21 +7,21 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { setAlertFlyoutVisible } from '../../../../state/actions'; -import { selectAlertFlyoutVisibility } from '../../../../state/selectors'; -import { UptimeAlertsFlyoutWrapperComponent } from '../index'; +import { UptimeAlertsFlyoutWrapperComponent } from '../uptime_alerts_flyout_wrapper'; +import { selectAlertFlyoutVisibility, selectAlertFlyoutType } from '../../../../state/selectors'; interface Props { - alertTypeId?: string; canChangeTrigger?: boolean; } -export const UptimeAlertsFlyoutWrapper = ({ alertTypeId, canChangeTrigger }: Props) => { +export const UptimeAlertsFlyoutWrapper = ({ canChangeTrigger }: Props) => { const dispatch = useDispatch(); const setAddFlyoutVisibility = (value: React.SetStateAction) => // @ts-ignore the value here is a boolean, and it works with the action creator function dispatch(setAlertFlyoutVisible(value)); const alertFlyoutVisible = useSelector(selectAlertFlyoutVisibility); + const alertTypeId = useSelector(selectAlertFlyoutType); return ( ({ - setEsKueryFilters: (esFilters: string) => dispatch(setEsKueryString(esFilters)), -}); - -const mapStateToProps = (state: AppState) => ({ ...selectIndexPattern(state) }); - -export const OverviewPage = connect(mapStateToProps, mapDispatchToProps)(OverviewPageComponent); +export const OverviewPage: React.FC = props => { + const dispatch = useDispatch(); + const setEsKueryFilters = useCallback( + (esFilters: string) => dispatch(setEsKueryString(esFilters)), + [dispatch] + ); + const indexPattern = useSelector(selectIndexPattern); + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index adc36efa6f7db..faf8c88521186 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -11,22 +11,22 @@ import { i18n } from '@kbn/i18n'; import { useUptimeTelemetry, UptimePage, useGetUrlParams } from '../hooks'; import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useTrackPageview } from '../../../../../plugins/observability/public'; -import { DataPublicPluginSetup, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { IIndexPattern } from '../../../../../../src/plugins/data/public'; import { useUpdateKueryString } from '../hooks'; import { PageHeader } from './page_header'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { MonitorList } from '../components/overview/monitor_list/monitor_list_container'; import { EmptyState, FilterGroup, KueryBar, ParsingErrorCallout } from '../components/overview'; import { StatusPanel } from '../components/overview/status_panel'; +import { OverviewPageProps } from '../components/overview/overview_container'; -interface OverviewPageProps { - autocomplete: DataPublicPluginSetup['autocomplete']; +interface Props extends OverviewPageProps { indexPattern: IIndexPattern | null; setEsKueryFilters: (esFilters: string) => void; + currentAlertType: string; + setAlertType: (alertType: string) => void; } -type Props = OverviewPageProps; - const EuiFlexItemStyled = styled(EuiFlexItem)` && { min-width: 598px; @@ -36,7 +36,13 @@ const EuiFlexItemStyled = styled(EuiFlexItem)` } `; -export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFilters }: Props) => { +export const OverviewPageComponent = ({ + autocomplete, + currentAlertType, + indexPattern, + setAlertType, + setEsKueryFilters, +}: Props) => { const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); const { search, filters: urlFilters } = params; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts index 4885f974dbbd4..3ad9ffc4a8fb0 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts @@ -14,6 +14,8 @@ export type UiPayload = PopoverState & string & number & Map; export const setAlertFlyoutVisible = createAction('TOGGLE ALERT FLYOUT'); +export const setAlertFlyoutType = createAction('SET ALERT FLYOUT TYPE'); + export const setBasePath = createAction('SET BASE PATH'); export const triggerAppRefresh = createAction('REFRESH APP'); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts index c533f293fc940..5bf705a3f1eee 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts @@ -12,11 +12,15 @@ import { setEsKueryString, triggerAppRefresh, UiPayload, + setAlertFlyoutType, setAlertFlyoutVisible, } from '../actions'; +import { CLIENT_ALERT_TYPES } from '../../../common/constants'; + export interface UiState { alertFlyoutVisible: boolean; + alertFlyoutType: string; basePath: string; esKuery: string; integrationsPopoverOpen: PopoverState | null; @@ -25,6 +29,7 @@ export interface UiState { const initialState: UiState = { alertFlyoutVisible: false, + alertFlyoutType: CLIENT_ALERT_TYPES.MONITOR_STATUS, basePath: '', esKuery: '', integrationsPopoverOpen: null, @@ -57,6 +62,11 @@ export const uiReducer = handleActions( ...state, esKuery: action.payload as string, }), + + [String(setAlertFlyoutType)]: (state, action: Action) => ({ + ...state, + alertFlyoutType: action.payload, + }), }, initialState ); diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 15fc8b8a7b173..9552538182dd2 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -91,6 +91,8 @@ export const selectDurationLines = ({ monitorDuration }: AppState) => { export const selectAlertFlyoutVisibility = ({ ui: { alertFlyoutVisible } }: AppState) => alertFlyoutVisible; +export const selectAlertFlyoutType = ({ ui: { alertFlyoutType } }: AppState) => alertFlyoutType; + export const selectMonitorStatusAlert = ({ indexPattern, overviewFilters, ui }: AppState) => ({ filters: ui.esKuery, indexPattern: indexPattern.index_pattern, diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index 92775a2663863..80dedeb91da8a 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -102,10 +102,7 @@ const Application = (props: UptimeAppProps) => {
- +
From 5870a59488356217c176f4f26a1365a2741c703f Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 22 Apr 2020 19:45:00 -0400 Subject: [PATCH 18/49] Finish adding alert type for client, add server alert summary. --- .../plugins/uptime/common/constants/index.ts | 2 +- .../components/overview/alerts/alert_tls.tsx | 120 +++++++++++ .../alerts/alerts_containers/alert_tls.tsx | 26 +++ .../toggle_alert_flyout_button.tsx | 11 +- .../uptime_alerts_flyout_wrapper.tsx | 7 +- .../alerts/toggle_alert_flyout_button.tsx | 36 ++-- .../overview/alerts/translations.ts | 64 ++++++ .../alerts/uptime_alerts_flyout_wrapper.tsx | 6 +- .../uptime/public/lib/alert_types/index.ts | 6 +- .../public/lib/alert_types/monitor_status.tsx | 14 +- .../uptime/public/lib/alert_types/tls.tsx | 30 +++ .../public/lib/alert_types/translations.ts | 31 +++ .../plugins/uptime/public/pages/overview.tsx | 10 +- .../plugins/uptime/public/state/actions/ui.ts | 2 +- .../uptime/public/state/reducers/ui.ts | 5 +- .../plugins/uptime/public/uptime_app.tsx | 2 +- .../server/lib/alerts/__tests__/tls.test.ts | 191 ++++++++++++++++++ .../plugins/uptime/server/lib/alerts/tls.ts | 96 +++++++-- 18 files changed, 595 insertions(+), 64 deletions(-) create mode 100644 x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx create mode 100644 x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx create mode 100644 x-pack/legacy/plugins/uptime/public/components/overview/alerts/translations.ts create mode 100644 x-pack/legacy/plugins/uptime/public/lib/alert_types/tls.tsx create mode 100644 x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts create mode 100644 x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts diff --git a/x-pack/legacy/plugins/uptime/common/constants/index.ts b/x-pack/legacy/plugins/uptime/common/constants/index.ts index 72d498056d6b3..ba07710e2e7bd 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/index.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ACTION_GROUP_DEFINITIONS } from './alerts'; +export * from './alerts'; export { CHART_FORMAT_LIMITS } from './chart_format_limits'; export { CLIENT_DEFAULTS } from './client_defaults'; export { CONTEXT_DEFAULTS } from './context_defaults'; diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx new file mode 100644 index 0000000000000..aa533cd8fe454 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx @@ -0,0 +1,120 @@ +/* + * 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 { + EuiExpression, + EuiFlexItem, + EuiFlexGroup, + EuiLink, + EuiPopover, + EuiSpacer, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { TlsTranslations } from './translations'; + +interface SettingsMessageExpressionPopoverProps { + 'aria-label': string; + description: string; + id: string; + setAlertFlyoutVisible: (value: boolean) => void; + value: string; +} + +const SettingsMessageExpressionPopover: React.FC = ({ + 'aria-label': ariaLabel, + description, + setAlertFlyoutVisible, + value, + id, +}) => { + const [isOpen, setIsOpen] = useState(false); + const kibana = useKibana(); + const href = kibana.services?.application?.getUrlForApp('uptime#/settings'); + return ( + setIsOpen(!isOpen)} + value={value} + /> + } + isOpen={isOpen} + closePopover={() => setIsOpen(false)} + > + + { + setAlertFlyoutVisible(false); + }} + onKeyUp={e => { + if (e.key === 'Enter') { + setAlertFlyoutVisible(false); + } + }} + > + settings page + + + ), + }} + /> + + ); +}; + +interface Props { + ageThreshold?: number; + expirationThreshold?: number; + setAlertFlyoutVisible: (value: boolean) => void; +} + +export const AlertTlsComponent: React.FC = props => ( + <> + + + + + + + + + + + + + + +); diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx new file mode 100644 index 0000000000000..6eca449ca8585 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx @@ -0,0 +1,26 @@ +/* + * 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 { useDispatch, useSelector } from 'react-redux'; +import React, { useCallback } from 'react'; +import { AlertTlsComponent } from '../alert_tls'; +import { setAlertFlyoutVisible } from '../../../../state/actions'; +import { selectDynamicSettings } from '../../../../state/selectors'; + +export const AlertTls = () => { + const dispatch = useDispatch(); + const setFlyoutVisible = useCallback((value: boolean) => dispatch(setAlertFlyoutVisible(value)), [ + dispatch, + ]); + const { settings } = useSelector(selectDynamicSettings); + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx index 45ba72d76fba6..68684e9a62f42 100644 --- a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx @@ -6,14 +6,21 @@ import React from 'react'; import { useDispatch } from 'react-redux'; -import { setAlertFlyoutVisible } from '../../../../state/actions'; +import { setAlertFlyoutType, setAlertFlyoutVisible } from '../../../../state/actions'; import { ToggleAlertFlyoutButtonComponent } from '../index'; export const ToggleAlertFlyoutButton = () => { const dispatch = useDispatch(); return ( dispatch(setAlertFlyoutVisible(value))} + setAlertFlyoutVisible={(value: boolean | string) => { + if (typeof value === 'string') { + dispatch(setAlertFlyoutType(value)); + dispatch(setAlertFlyoutVisible(true)); + } else { + dispatch(setAlertFlyoutVisible(value)); + } + }} /> ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx index 49f2954c8e7d6..33eafbd1e21bc 100644 --- a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/uptime_alerts_flyout_wrapper.tsx @@ -10,11 +10,7 @@ import { setAlertFlyoutVisible } from '../../../../state/actions'; import { UptimeAlertsFlyoutWrapperComponent } from '../uptime_alerts_flyout_wrapper'; import { selectAlertFlyoutVisibility, selectAlertFlyoutType } from '../../../../state/selectors'; -interface Props { - canChangeTrigger?: boolean; -} - -export const UptimeAlertsFlyoutWrapper = ({ canChangeTrigger }: Props) => { +export const UptimeAlertsFlyoutWrapper: React.FC = () => { const dispatch = useDispatch(); const setAddFlyoutVisibility = (value: React.SetStateAction) => // @ts-ignore the value here is a boolean, and it works with the action creator function @@ -27,7 +23,6 @@ export const UptimeAlertsFlyoutWrapper = ({ canChangeTrigger }: Props) => { ); diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index 04dfe4b3e3509..2b726bc68b2e5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -7,11 +7,12 @@ import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { CLIENT_ALERT_TYPES } from '../../../../common/constants'; +import { ToggleFlyoutTranslations } from './translations'; interface Props { - setAlertFlyoutVisible: (value: boolean) => void; + setAlertFlyoutVisible: (value: boolean | string) => void; } export const ToggleAlertFlyoutButtonComponent = ({ setAlertFlyoutVisible }: Props) => { @@ -22,9 +23,7 @@ export const ToggleAlertFlyoutButtonComponent = ({ setAlertFlyoutVisible }: Prop { - setAlertFlyoutVisible(true); + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.MONITOR_STATUS); setIsOpen(false); }} > , { + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.TLS); + setIsOpen(false); + }} + > + + , + + i18n.translate('xpack.uptime.alerts.tls.expirationExpression.value', { + defaultMessage: '{value} days', + values: { value }, + }), + ageAriaLabel: i18n.translate('xpack.uptime.alerts.tls.ageExpression.ariaLabel', { + defaultMessage: + 'An expressing displaying the threshold that will trigger the TLS alert for old certificates', + }), + ageDescription: i18n.translate('xpack.uptime.alerts.tls.ageExpression.description', { + defaultMessage: 'or older than', + }), + ageValue: (value?: number) => + i18n.translate('xpack.uptime.alerts.tls.ageExpression.value', { + defaultMessage: '{value} days', + values: { value }, + }), +}; + +export const ToggleFlyoutTranslations = { + toggleButtonAriaLabel: i18n.translate('xpack.uptime.alertsPopover.toggleButton.ariaLabel', { + defaultMessage: 'Open alert context menu', + }), + toggleTlsAriaLabel: i18n.translate('xpack.uptime.toggleTlsAlertButton.ariaLabel', { + defaultMessage: 'Open TLS alert flyout', + }), + toggleMonitorStatusAriaLabel: i18n.translate('xpack.uptime.toggleAlertFlyout.ariaLabel', { + defaultMessage: 'Open add alert flyout', + }), + navigateToAlertingUIAriaLabel: i18n.translate('xpack.uptime.navigateToAlertingUi', { + defaultMessage: 'Leave Uptime and go to Alerting Management page', + }), +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx index 13705e7d19293..0992e71c1a4e8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx @@ -10,14 +10,12 @@ import { AlertAdd } from '../../../../../../../plugins/triggers_actions_ui/publi interface Props { alertFlyoutVisible: boolean; alertTypeId?: string; - canChangeTrigger?: boolean; setAlertFlyoutVisibility: React.Dispatch>; } export const UptimeAlertsFlyoutWrapperComponent = ({ alertFlyoutVisible, alertTypeId, - canChangeTrigger, setAlertFlyoutVisibility, }: Props) => ( ); diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/index.ts b/x-pack/legacy/plugins/uptime/public/lib/alert_types/index.ts index 74160577cb0b1..d2e16e8316575 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/index.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/index.ts @@ -8,7 +8,11 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../../../plugins/triggers_actions_ui/public'; import { initMonitorStatusAlertType } from './monitor_status'; +import { initTlsAlertType } from './tls'; export type AlertTypeInitializer = (dependenies: { autocomplete: any }) => AlertTypeModel; -export const alertTypeInitializers: AlertTypeInitializer[] = [initMonitorStatusAlertType]; +export const alertTypeInitializers: AlertTypeInitializer[] = [ + initMonitorStatusAlertType, + initTlsAlertType, +]; diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 0624d20b197c0..c267e80898d55 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -17,6 +17,8 @@ import { import { AlertTypeInitializer } from '.'; import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; import { AlertMonitorStatus } from '../../components/overview/alerts/alerts_containers'; +import { CLIENT_ALERT_TYPES } from '../../../common/constants'; +import { MonitorStatusTranslations } from './translations'; export const validate = (alertParams: any): ValidationResult => { const errors: Record = {}; @@ -57,15 +59,15 @@ export const validate = (alertParams: any): ValidationResult => { return { errors }; }; +const { name, defaultActionMessage } = MonitorStatusTranslations; + export const initMonitorStatusAlertType: AlertTypeInitializer = ({ autocomplete, }): AlertTypeModel => ({ - id: 'xpack.uptime.alerts.monitorStatus', - name: 'Uptime monitor status', + id: CLIENT_ALERT_TYPES.MONITOR_STATUS, + name, iconClass: 'uptimeApp', - alertParamsExpression: params => { - return ; - }, + alertParamsExpression: params => , validate, - defaultActionMessage: `{{context.message}}\nLast triggered at: {{state.lastTriggeredAt}}\n{{context.downMonitorsWithGeo}}`, + defaultActionMessage, }); diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/legacy/plugins/uptime/public/lib/alert_types/tls.tsx new file mode 100644 index 0000000000000..07cb26bdbc653 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/tls.tsx @@ -0,0 +1,30 @@ +/* + * 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 { PathReporter } from 'io-ts/lib/PathReporter'; +import React from 'react'; +import { isRight } from 'fp-ts/lib/Either'; +import { CLIENT_ALERT_TYPES } from '../../../common/constants'; +import { TlsTranslations } from './translations'; +import { + AlertTypeModel, + ValidationResult, +} from '../../../../../../plugins/triggers_action_ui/public/types'; +import { AlertTypeInitializer } from '.'; +import { AlertTls } from '../../components/overview/alerts/alerts_containers/alert_tls'; + +const { name, defaultActionMessage } = TlsTranslations; + +const validate = (): ValidationResult => ({}); + +export const initTlsAlertType: AlertTypeInitializer = (): AlertTypeModel => ({ + id: CLIENT_ALERT_TYPES.TLS, + iconClass: 'uptimeApp', + alertParamsExpression: () => , + name, + validate, + defaultActionMessage, +}); diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts new file mode 100644 index 0000000000000..fc61d146fb86e --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts @@ -0,0 +1,31 @@ +/* + * 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 MonitorStatusTranslations = { + defaultActionMessage: i18n.translate('xpack.uptime.alerts.monitorStatus.defaultActionMessage', { + defaultMessage: '{contextMessage}\nLast triggered at:{lastTriggered}\n{downMonitors}', + values: { + contextMessage: '{{context.message}}', + lastTriggered: '{{state.lastTriggeredAt}}', + downMonitors: '{{context.downMonitorsWithGeo}}', + }, + }), + name: i18n.translate('xpack.uptime.alerts.monitorStatus.clientName', { + defaultMessage: 'Uptime monitor status', + }), +}; + +export const TlsTranslations = { + defaultActionMessage: i18n.translate('xpack.uptime.alerts.tls.defaultActionMessage', { + // TODO: add something useful here + defaultMessage: 'TODO', + }), + name: i18n.translate('xpack.uptime.alerts.tls.clientName', { + defaultMessage: 'Uptime TLS', + }), +}; diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index faf8c88521186..9c1a9ead7af26 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -23,8 +23,6 @@ import { OverviewPageProps } from '../components/overview/overview_container'; interface Props extends OverviewPageProps { indexPattern: IIndexPattern | null; setEsKueryFilters: (esFilters: string) => void; - currentAlertType: string; - setAlertType: (alertType: string) => void; } const EuiFlexItemStyled = styled(EuiFlexItem)` @@ -36,13 +34,7 @@ const EuiFlexItemStyled = styled(EuiFlexItem)` } `; -export const OverviewPageComponent = ({ - autocomplete, - currentAlertType, - indexPattern, - setAlertType, - setEsKueryFilters, -}: Props) => { +export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFilters }: Props) => { const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); const { search, filters: urlFilters } = params; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts index 3ad9ffc4a8fb0..80e8796843ac2 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts @@ -12,7 +12,7 @@ export interface PopoverState { export type UiPayload = PopoverState & string & number & Map; -export const setAlertFlyoutVisible = createAction('TOGGLE ALERT FLYOUT'); +export const setAlertFlyoutVisible = createAction('TOGGLE ALERT FLYOUT'); export const setAlertFlyoutType = createAction('SET ALERT FLYOUT TYPE'); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts index 5bf705a3f1eee..82c2bfe2c0cec 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts @@ -16,11 +16,9 @@ import { setAlertFlyoutVisible, } from '../actions'; -import { CLIENT_ALERT_TYPES } from '../../../common/constants'; - export interface UiState { alertFlyoutVisible: boolean; - alertFlyoutType: string; + alertFlyoutType?: string; basePath: string; esKuery: string; integrationsPopoverOpen: PopoverState | null; @@ -29,7 +27,6 @@ export interface UiState { const initialState: UiState = { alertFlyoutVisible: false, - alertFlyoutType: CLIENT_ALERT_TYPES.MONITOR_STATUS, basePath: '', esKuery: '', integrationsPopoverOpen: null, diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index 80dedeb91da8a..a497d10b6de61 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -102,7 +102,7 @@ const Application = (props: UptimeAppProps) => {
- +
diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts new file mode 100644 index 0000000000000..4525c52b08fdb --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts @@ -0,0 +1,191 @@ +/* + * 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 { getCertSummary } from '../tls'; +import { Cert } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; + +describe('tls alert', () => { + describe('getCertSummary', () => { + let mockCerts: Cert[]; + + beforeEach(() => { + mockCerts = [ + { + certificate_not_valid_after: '2020-07-16T03:15:39.000Z', + certificate_not_valid_before: '2019-07-24T03:15:39.000Z', + common_name: 'Common-One', + monitors: [{ name: 'monitor-one', id: 'monitor1' }], + sha256: 'abc', + }, + { + certificate_not_valid_after: '2020-07-18T03:15:39.000Z', + certificate_not_valid_before: '2019-07-20T03:15:39.000Z', + common_name: 'Common-Two', + monitors: [{ name: 'monitor-two', id: 'monitor2' }], + sha256: 'bcd', + }, + { + certificate_not_valid_after: '2020-07-19T03:15:39.000Z', + certificate_not_valid_before: '2019-07-22T03:15:39.000Z', + common_name: 'Common-Three', + monitors: [{ name: 'monitor-three', id: 'monitor3' }], + sha256: 'cde', + }, + { + certificate_not_valid_after: '2020-07-25T03:15:39.000Z', + certificate_not_valid_before: '2019-07-25T03:15:39.000Z', + common_name: 'Common-Four', + monitors: [{ name: 'monitor-four', id: 'monitor4' }], + sha256: 'def', + }, + ]; + }); + + it('sorts expiring certs appropriately when creating summary', () => { + const result = getCertSummary( + mockCerts, + new Date('2020-07-20T05:00:00.000Z').valueOf(), + new Date('2019-03-01T00:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "aging": Array [], + "expiring": Array [ + Object { + "certificate_not_valid_after": "2020-07-16T03:15:39.000Z", + "certificate_not_valid_before": "2019-07-24T03:15:39.000Z", + "common_name": "Common-One", + "monitors": Array [ + Object { + "id": "monitor1", + "name": "monitor-one", + }, + ], + "sha256": "abc", + }, + Object { + "certificate_not_valid_after": "2020-07-18T03:15:39.000Z", + "certificate_not_valid_before": "2019-07-20T03:15:39.000Z", + "common_name": "Common-Two", + "monitors": Array [ + Object { + "id": "monitor2", + "name": "monitor-two", + }, + ], + "sha256": "bcd", + }, + Object { + "certificate_not_valid_after": "2020-07-19T03:15:39.000Z", + "certificate_not_valid_before": "2019-07-22T03:15:39.000Z", + "common_name": "Common-Three", + "monitors": Array [ + Object { + "id": "monitor3", + "name": "monitor-three", + }, + ], + "sha256": "cde", + }, + ], + "topAging": Array [], + "topExpiring": Array [ + Object { + "commonName": "Common-One", + "validUntil": "2020-07-16T03:15:39.000Z", + }, + Object { + "commonName": "Common-Two", + "validUntil": "2020-07-18T03:15:39.000Z", + }, + Object { + "commonName": "Common-Three", + "validUntil": "2020-07-19T03:15:39.000Z", + }, + ], + } + `); + }); + + it('sorts aging certs appropriate when creating summary', () => { + const result = getCertSummary( + mockCerts, + new Date('2020-07-01T12:00:00.000Z').valueOf(), + new Date('2019-09-01T03:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "aging": Array [ + Object { + "certificate_not_valid_after": "2020-07-18T03:15:39.000Z", + "certificate_not_valid_before": "2019-07-20T03:15:39.000Z", + "common_name": "Common-Two", + "monitors": Array [ + Object { + "id": "monitor2", + "name": "monitor-two", + }, + ], + "sha256": "bcd", + }, + Object { + "certificate_not_valid_after": "2020-07-19T03:15:39.000Z", + "certificate_not_valid_before": "2019-07-22T03:15:39.000Z", + "common_name": "Common-Three", + "monitors": Array [ + Object { + "id": "monitor3", + "name": "monitor-three", + }, + ], + "sha256": "cde", + }, + Object { + "certificate_not_valid_after": "2020-07-16T03:15:39.000Z", + "certificate_not_valid_before": "2019-07-24T03:15:39.000Z", + "common_name": "Common-One", + "monitors": Array [ + Object { + "id": "monitor1", + "name": "monitor-one", + }, + ], + "sha256": "abc", + }, + Object { + "certificate_not_valid_after": "2020-07-25T03:15:39.000Z", + "certificate_not_valid_before": "2019-07-25T03:15:39.000Z", + "common_name": "Common-Four", + "monitors": Array [ + Object { + "id": "monitor4", + "name": "monitor-four", + }, + ], + "sha256": "def", + }, + ], + "expiring": Array [], + "topAging": Array [ + Object { + "commonName": "Common-Two", + "validAfter": "2019-07-20T03:15:39.000Z", + }, + Object { + "commonName": "Common-Three", + "validAfter": "2019-07-22T03:15:39.000Z", + }, + Object { + "commonName": "Common-One", + "validAfter": "2019-07-24T03:15:39.000Z", + }, + ], + "topExpiring": Array [], + } + `); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 89211c70831e8..fda4fa7152fb6 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -4,29 +4,67 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { UptimeAlertTypeFactory } from './types'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; -import { ACTION_GROUP_DEFINITIONS } from '../../../../../legacy/plugins/uptime/common/constants'; +import { + ACTION_GROUP_DEFINITIONS, + DYNAMIC_SETTINGS_DEFAULTS, +} from '../../../../../legacy/plugins/uptime/common/constants'; +import { Cert } from '../../../../../legacy/plugins/uptime/common/runtime_types'; const { TLS } = ACTION_GROUP_DEFINITIONS; +const DEFAULT_FROM = 'now-1d'; +const DEFAULT_TO = 'now'; +const DEFAULT_INDEX = 0; +const DEFAULT_SIZE = 1000; + +const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf(); + +export const getCertSummary = ( + certs: Cert[], + expirationThreshold: number, + ageThreshold: number, + maxSummaryItems: number = 1000, + topLength: number = 3 +) => { + certs.sort((a, b) => + sortCerts(a.certificate_not_valid_after ?? '', b.certificate_not_valid_after ?? '') + ); + const expiring = certs.filter( + cert => new Date(cert.certificate_not_valid_after ?? '').valueOf() < expirationThreshold + ); + certs.sort((a, b) => + sortCerts(a.certificate_not_valid_before ?? '', b.certificate_not_valid_before ?? '') + ); + const aging = certs.filter( + cert => new Date(cert.certificate_not_valid_before ?? '').valueOf() < ageThreshold + ); + return { + expiring: expiring.slice(0, maxSummaryItems), + topExpiring: expiring.slice(0, topLength).map(cert => ({ + commonName: cert.common_name, + validUntil: cert.certificate_not_valid_after, + })), + aging: aging.slice(0, maxSummaryItems), + topAging: aging.slice(0, topLength).map(cert => ({ + commonName: cert.common_name, + validAfter: cert.certificate_not_valid_before, + })), + }; +}; + export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ id: 'xpack.uptime.alerts.tls', name: i18n.translate('xpack/uptime.alerts.tls', { defaultMessage: 'Uptime TLS', }), validate: { - params: schema.object({ - from: schema.string(), - to: schema.string(), - index: schema.number(), - size: schema.number(), - search: schema.maybe(schema.string()), - filters: schema.maybe(schema.string()), - }), + params: schema.object({}), }, defaultActionGroupId: TLS.id, actionGroups: [ @@ -36,8 +74,12 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ }, ], actionVariables: { - context: [], - state: [], + context: [ + // TODO + ], + state: [ + // TODO + ], }, async executor(options) { const { @@ -46,20 +88,40 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ state, } = options; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); + const certs = await libs.requests.getCerts({ callES: callCluster, dynamicSettings, - from, - to, - index, - size, + from: from || DEFAULT_FROM, + to: to || DEFAULT_TO, + index: index || DEFAULT_INDEX, + size: size || DEFAULT_SIZE, search, + notValidAfter: `now+${dynamicSettings.certificatesThresholds?.expiration ?? + DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.expiration}d`, + notValidBefore: `now-${dynamicSettings.certificatesThresholds?.age ?? + DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.age}d`, }); if (certs.length) { + const absoluteExpirationThreshold = moment() + .add( + dynamicSettings.certificatesThresholds?.expiration ?? + DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.expiration, + 'd' + ) + .valueOf(); + const absoluteAgeThreshold = moment() + .subtract( + dynamicSettings.certificatesThresholds?.age ?? + DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.age, + 'd' + ) + .valueOf(); const alertInstance = alertInstanceFactory(TLS.id); - // this is not scalable - alertInstance.replaceState({ certs }); + alertInstance.replaceState({ + ...getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold), + }); alertInstance.scheduleActions(TLS.id); } From 4c94906ad4267edfb7e097e82d5ca4aa281b09a6 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 17:58:59 -0400 Subject: [PATCH 19/49] Add some state variables. --- .../alerts/alerts_containers/alert_tls.tsx | 4 +- .../public/lib/alert_types/translations.ts | 15 +++- .../plugins/uptime/server/lib/alerts/tls.ts | 71 +++++++++++++------ .../uptime/server/lib/requests/get_certs.ts | 7 +- 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx index 6eca449ca8585..ab24d0fb5ee67 100644 --- a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_tls.tsx @@ -18,8 +18,8 @@ export const AlertTls = () => { const { settings } = useSelector(selectDynamicSettings); return ( ); diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts index fc61d146fb86e..98f1f2b23f46e 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts @@ -22,8 +22,19 @@ export const MonitorStatusTranslations = { export const TlsTranslations = { defaultActionMessage: i18n.translate('xpack.uptime.alerts.tls.defaultActionMessage', { - // TODO: add something useful here - defaultMessage: 'TODO', + defaultMessage: `Detected {count} TLS certificates expiring or becoming too old. + +{expiringCount} expiring common names, including {expiringCommonNameAndDate}. + +{agingCount} aged common names, including {agingCommonNameAndDate}. +`, + values: { + count: '{{state.count}}', + expiringCount: '{{state.expiringCount}}', + expiringCommonNameAndDate: '{{state.expiringCommonNameAndDate}}', + agingCount: '{{state.agingCount}}', + agingCommonNameAndDate: '{{state.agingCommonNameAndDate}}', + }, }), name: i18n.translate('xpack.uptime.alerts.tls.clientName', { defaultMessage: 'Uptime TLS', diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index fda4fa7152fb6..6e412e5c5336e 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -18,10 +18,10 @@ import { Cert } from '../../../../../legacy/plugins/uptime/common/runtime_types' const { TLS } = ACTION_GROUP_DEFINITIONS; -const DEFAULT_FROM = 'now-1d'; +const DEFAULT_FROM = 'now-7d'; const DEFAULT_TO = 'now'; const DEFAULT_INDEX = 0; -const DEFAULT_SIZE = 1000; +const DEFAULT_SIZE = 20; const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf(); @@ -74,16 +74,49 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ }, ], actionVariables: { - context: [ - // TODO - ], + context: [], state: [ - // TODO + { + name: 'count', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.count', { + defaultMessage: 'The number of certs detected by the alert executor', + }), + }, + { + name: 'expiringCount', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.expiringCount', { + defaultMessage: 'The number of expiring certs detected by the alert.', + }), + }, + { + name: 'expiringCommonNameAndDate', + description: i18n.translate( + 'xpack.uptime.alerts.tls.actionVariables.state.expiringCommonNameAndDate', + { + defaultMessage: 'The common names and expiration date/time of the detected certs', + } + ), + }, + { + name: 'agingCount', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.agingCount', { + defaultMessage: 'The number of detected certs that are becoming too old.', + }), + }, + + { + name: 'agingCommonNameAndDate', + description: i18n.translate( + 'xpack.uptime.alerts.tls.actionVariables.state.agingCommonNameAndDate', + { + defaultMessage: 'The common names and expiration date/time of the detected certs.', + } + ), + }, ], }, async executor(options) { const { - params: { from, to, index, size, search }, services: { alertInstanceFactory, callCluster, savedObjectsClient }, state, } = options; @@ -92,29 +125,27 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ const certs = await libs.requests.getCerts({ callES: callCluster, dynamicSettings, - from: from || DEFAULT_FROM, - to: to || DEFAULT_TO, - index: index || DEFAULT_INDEX, - size: size || DEFAULT_SIZE, - search, - notValidAfter: `now+${dynamicSettings.certificatesThresholds?.expiration ?? - DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.expiration}d`, - notValidBefore: `now-${dynamicSettings.certificatesThresholds?.age ?? - DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.age}d`, + from: DEFAULT_FROM, + to: DEFAULT_TO, + index: DEFAULT_INDEX, + size: DEFAULT_SIZE, + notValidAfter: `now+${dynamicSettings.certThresholds?.expiration ?? + DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.expiration}d`, + notValidBefore: `now-${dynamicSettings.certThresholds?.age ?? + DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.age}d`, }); if (certs.length) { const absoluteExpirationThreshold = moment() .add( - dynamicSettings.certificatesThresholds?.expiration ?? - DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.expiration, + dynamicSettings.certThresholds?.expiration ?? + DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.expiration, 'd' ) .valueOf(); const absoluteAgeThreshold = moment() .subtract( - dynamicSettings.certificatesThresholds?.age ?? - DYNAMIC_SETTINGS_DEFAULTS.certificatesThresholds?.age, + dynamicSettings.certThresholds?.age ?? DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.age, 'd' ) .valueOf(); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 6bd85834fe237..00ef78d26e697 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -69,6 +69,9 @@ export const getCerts: UMElasticsearchQueryFn = async ({ }, }; + if (search || notValidBefore || notValidAfter) { + params.body.query.bool.should = []; + } if (search) { params.body.query.bool.should = [ { @@ -103,7 +106,7 @@ export const getCerts: UMElasticsearchQueryFn = async ({ } if (notValidBefore) { - params.body.query.bool.filter.push({ + params.body.query.bool.should.push({ range: { 'tls.certificate_not_valid_before': { lte: notValidBefore, @@ -113,7 +116,7 @@ export const getCerts: UMElasticsearchQueryFn = async ({ } if (notValidAfter) { - params.body.query.bool.filter.push({ + params.body.query.bool.should.push({ range: { 'tls.certificate_not_valid_after': { lte: notValidAfter, From 6e0b46999fa0f97f7e37afa6fb59381fd3ad8653 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 23 Apr 2020 18:15:23 -0400 Subject: [PATCH 20/49] Update certs summary function to create required values. --- .../plugins/uptime/server/lib/alerts/tls.ts | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 6e412e5c5336e..a8d517dffddbb 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -25,36 +25,46 @@ const DEFAULT_SIZE = 20; const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf(); +const mapCertsToSummaryString = (certs: Cert[], maxSummaryItems: number): string => + certs + .slice(0, maxSummaryItems) + .map(cert => `${cert.common_name} expiration date ${cert.certificate_not_valid_after}`) + .reduce((prev, cur) => prev.concat(`, ${cur}`), ''); + +interface TlsAlertState { + count: number; + agingCount: number; + agingCommonNameAndDate: string; + expiringCount: number; + expiringCommonNameAndDate: string; +} + export const getCertSummary = ( certs: Cert[], expirationThreshold: number, ageThreshold: number, - maxSummaryItems: number = 1000, - topLength: number = 3 -) => { + maxSummaryItems: number = 3 +): TlsAlertState => { certs.sort((a, b) => sortCerts(a.certificate_not_valid_after ?? '', b.certificate_not_valid_after ?? '') ); const expiring = certs.filter( cert => new Date(cert.certificate_not_valid_after ?? '').valueOf() < expirationThreshold ); + certs.sort((a, b) => sortCerts(a.certificate_not_valid_before ?? '', b.certificate_not_valid_before ?? '') ); const aging = certs.filter( cert => new Date(cert.certificate_not_valid_before ?? '').valueOf() < ageThreshold ); + return { - expiring: expiring.slice(0, maxSummaryItems), - topExpiring: expiring.slice(0, topLength).map(cert => ({ - commonName: cert.common_name, - validUntil: cert.certificate_not_valid_after, - })), - aging: aging.slice(0, maxSummaryItems), - topAging: aging.slice(0, topLength).map(cert => ({ - commonName: cert.common_name, - validAfter: cert.certificate_not_valid_before, - })), + count: certs.length, + agingCount: aging.length, + agingCommonNameAndDate: mapCertsToSummaryString(aging, maxSummaryItems), + expiringCommonNameAndDate: mapCertsToSummaryString(expiring, maxSummaryItems), + expiringCount: expiring.length, }; }; From 36a9ee16b53a10d79c13c9c2d316473adb693791 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Apr 2020 13:42:22 -0400 Subject: [PATCH 21/49] Refresh test snapshots. --- .../server/lib/alerts/__tests__/tls.test.ts | 130 ++---------------- 1 file changed, 10 insertions(+), 120 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts index 4525c52b08fdb..a716664b8b6b8 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts @@ -52,60 +52,11 @@ describe('tls alert', () => { ); expect(result).toMatchInlineSnapshot(` Object { - "aging": Array [], - "expiring": Array [ - Object { - "certificate_not_valid_after": "2020-07-16T03:15:39.000Z", - "certificate_not_valid_before": "2019-07-24T03:15:39.000Z", - "common_name": "Common-One", - "monitors": Array [ - Object { - "id": "monitor1", - "name": "monitor-one", - }, - ], - "sha256": "abc", - }, - Object { - "certificate_not_valid_after": "2020-07-18T03:15:39.000Z", - "certificate_not_valid_before": "2019-07-20T03:15:39.000Z", - "common_name": "Common-Two", - "monitors": Array [ - Object { - "id": "monitor2", - "name": "monitor-two", - }, - ], - "sha256": "bcd", - }, - Object { - "certificate_not_valid_after": "2020-07-19T03:15:39.000Z", - "certificate_not_valid_before": "2019-07-22T03:15:39.000Z", - "common_name": "Common-Three", - "monitors": Array [ - Object { - "id": "monitor3", - "name": "monitor-three", - }, - ], - "sha256": "cde", - }, - ], - "topAging": Array [], - "topExpiring": Array [ - Object { - "commonName": "Common-One", - "validUntil": "2020-07-16T03:15:39.000Z", - }, - Object { - "commonName": "Common-Two", - "validUntil": "2020-07-18T03:15:39.000Z", - }, - Object { - "commonName": "Common-Three", - "validUntil": "2020-07-19T03:15:39.000Z", - }, - ], + "agingCommonNameAndDate": "", + "agingCount": 0, + "count": 4, + "expiringCommonNameAndDate": "Common-One, expires: 2020-07-16T03:15:39.000Z; Common-Two, expires: 2020-07-18T03:15:39.000Z; Common-Three, expires: 2020-07-19T03:15:39.000Z", + "expiringCount": 3, } `); }); @@ -118,72 +69,11 @@ describe('tls alert', () => { ); expect(result).toMatchInlineSnapshot(` Object { - "aging": Array [ - Object { - "certificate_not_valid_after": "2020-07-18T03:15:39.000Z", - "certificate_not_valid_before": "2019-07-20T03:15:39.000Z", - "common_name": "Common-Two", - "monitors": Array [ - Object { - "id": "monitor2", - "name": "monitor-two", - }, - ], - "sha256": "bcd", - }, - Object { - "certificate_not_valid_after": "2020-07-19T03:15:39.000Z", - "certificate_not_valid_before": "2019-07-22T03:15:39.000Z", - "common_name": "Common-Three", - "monitors": Array [ - Object { - "id": "monitor3", - "name": "monitor-three", - }, - ], - "sha256": "cde", - }, - Object { - "certificate_not_valid_after": "2020-07-16T03:15:39.000Z", - "certificate_not_valid_before": "2019-07-24T03:15:39.000Z", - "common_name": "Common-One", - "monitors": Array [ - Object { - "id": "monitor1", - "name": "monitor-one", - }, - ], - "sha256": "abc", - }, - Object { - "certificate_not_valid_after": "2020-07-25T03:15:39.000Z", - "certificate_not_valid_before": "2019-07-25T03:15:39.000Z", - "common_name": "Common-Four", - "monitors": Array [ - Object { - "id": "monitor4", - "name": "monitor-four", - }, - ], - "sha256": "def", - }, - ], - "expiring": Array [], - "topAging": Array [ - Object { - "commonName": "Common-Two", - "validAfter": "2019-07-20T03:15:39.000Z", - }, - Object { - "commonName": "Common-Three", - "validAfter": "2019-07-22T03:15:39.000Z", - }, - Object { - "commonName": "Common-One", - "validAfter": "2019-07-24T03:15:39.000Z", - }, - ], - "topExpiring": Array [], + "agingCommonNameAndDate": "Common-Two, expires: 2020-07-18T03:15:39.000Z; Common-Three, expires: 2020-07-19T03:15:39.000Z; Common-One, expires: 2020-07-16T03:15:39.000Z", + "agingCount": 4, + "count": 4, + "expiringCommonNameAndDate": "", + "expiringCount": 0, } `); }); From 6f4365f364283ecfc6ec4e29928ec16f61be33d7 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Apr 2020 13:44:27 -0400 Subject: [PATCH 22/49] Clean up message generator function. --- x-pack/plugins/uptime/server/lib/alerts/tls.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index a8d517dffddbb..c552dada6a895 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -28,8 +28,8 @@ const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b). const mapCertsToSummaryString = (certs: Cert[], maxSummaryItems: number): string => certs .slice(0, maxSummaryItems) - .map(cert => `${cert.common_name} expiration date ${cert.certificate_not_valid_after}`) - .reduce((prev, cur) => prev.concat(`, ${cur}`), ''); + .map(cert => `${cert.common_name}, expires: ${cert.certificate_not_valid_after}`) + .reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), ''); interface TlsAlertState { count: number; From 1a339d1b6279b8b1ace8188d411ffc9840b410f1 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Apr 2020 15:53:37 -0400 Subject: [PATCH 23/49] Add a comment. --- .../uptime/public/components/overview/alerts/alert_tls.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx index aa533cd8fe454..2633c12f74d3f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/overview/alerts/alert_tls.tsx @@ -57,6 +57,8 @@ const SettingsMessageExpressionPopover: React.FC { From e5febceeee949b1bba55f4418e2fb2de6cd32023 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Apr 2020 15:54:17 -0400 Subject: [PATCH 24/49] Update formatting for alert messages, add flags denoting presence of age/expiration data. --- .../public/lib/alert_types/translations.ts | 16 +++++++-- .../plugins/uptime/server/lib/alerts/tls.ts | 36 +++++++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts index 98f1f2b23f46e..abb72f7996fd2 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts @@ -24,16 +24,26 @@ export const TlsTranslations = { defaultActionMessage: i18n.translate('xpack.uptime.alerts.tls.defaultActionMessage', { defaultMessage: `Detected {count} TLS certificates expiring or becoming too old. -{expiringCount} expiring common names, including {expiringCommonNameAndDate}. - -{agingCount} aged common names, including {agingCommonNameAndDate}. +{expiringConditionalOpen} +Expiring cert count: {expiringCount} +Expiring Certificates: {expiringCommonNameAndDate}. +{expiringConditionalClose} + +{agingConditionalOpen} +Aging cert count: {agingCount} +Aging Certificates: {agingCommonNameAndDate}. +{agingConditionalClose} `, values: { count: '{{state.count}}', expiringCount: '{{state.expiringCount}}', expiringCommonNameAndDate: '{{state.expiringCommonNameAndDate}}', + expiringConditionalOpen: '{{#state.hasExpired}}', + expiringConditionalClose: '{{/state.hasExpired}}', agingCount: '{{state.agingCount}}', agingCommonNameAndDate: '{{state.agingCommonNameAndDate}}', + agingConditionalOpen: '{{#state.hasAging}}', + agingConditionalClose: '{{/state.hasAging}}', }, }), name: i18n.translate('xpack.uptime.alerts.tls.clientName', { diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index c552dada6a895..0ae0079cecc2c 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -25,10 +25,15 @@ const DEFAULT_SIZE = 20; const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf(); -const mapCertsToSummaryString = (certs: Cert[], maxSummaryItems: number): string => +const mapCertsToSummaryString = ( + certs: Cert[], + getCertValue: (cert: Cert) => string, + expirationVerb: 'expires' | 'valid after date', + maxSummaryItems: number +): string => certs .slice(0, maxSummaryItems) - .map(cert => `${cert.common_name}, expires: ${cert.certificate_not_valid_after}`) + .map(cert => `${cert.common_name}, ${expirationVerb}: ${getCertValue(cert)}`) .reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), ''); interface TlsAlertState { @@ -37,8 +42,14 @@ interface TlsAlertState { agingCommonNameAndDate: string; expiringCount: number; expiringCommonNameAndDate: string; + hasAging: true | null; + hasExpired: true | null; } +const getValidAfter = (cert: Cert) => cert.certificate_not_valid_after ?? ''; + +const getValidBefore = (cert: Cert) => cert.certificate_not_valid_before ?? ''; + export const getCertSummary = ( certs: Cert[], expirationThreshold: number, @@ -62,13 +73,25 @@ export const getCertSummary = ( return { count: certs.length, agingCount: aging.length, - agingCommonNameAndDate: mapCertsToSummaryString(aging, maxSummaryItems), - expiringCommonNameAndDate: mapCertsToSummaryString(expiring, maxSummaryItems), + agingCommonNameAndDate: mapCertsToSummaryString( + aging, + getValidBefore, + 'valid after date', + maxSummaryItems + ), + expiringCommonNameAndDate: mapCertsToSummaryString( + expiring, + getValidAfter, + 'expires', + maxSummaryItems + ), expiringCount: expiring.length, + hasAging: aging.length > 0 ? true : null, + hasExpired: expiring.length > 0 ? true : null, }; }; -export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ +export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.tls', name: i18n.translate('xpack/uptime.alerts.tls', { defaultMessage: 'Uptime TLS', @@ -160,8 +183,9 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ ) .valueOf(); const alertInstance = alertInstanceFactory(TLS.id); + const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); alertInstance.replaceState({ - ...getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold), + ...summary, }); alertInstance.scheduleActions(TLS.id); } From 86a1ff94f623e25b9c508ed97bf1bf2df4d9b143 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Apr 2020 17:06:16 -0400 Subject: [PATCH 25/49] Add relative date information to tls alert messages. --- .../server/lib/alerts/__tests__/tls.test.ts | 69 ++++++++++++++++++- .../plugins/uptime/server/lib/alerts/tls.ts | 55 +++++++-------- 2 files changed, 95 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts index a716664b8b6b8..2a5bdbf475221 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts @@ -4,14 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; import { getCertSummary } from '../tls'; import { Cert } from '../../../../../../legacy/plugins/uptime/common/runtime_types'; describe('tls alert', () => { describe('getCertSummary', () => { let mockCerts: Cert[]; + let diffSpy: jest.SpyInstance; beforeEach(() => { + diffSpy = jest.spyOn(moment.prototype, 'diff'); mockCerts = [ { certificate_not_valid_after: '2020-07-16T03:15:39.000Z', @@ -44,7 +47,15 @@ describe('tls alert', () => { ]; }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('sorts expiring certs appropriately when creating summary', () => { + diffSpy + .mockReturnValueOnce(900) + .mockReturnValueOnce(901) + .mockReturnValueOnce(902); const result = getCertSummary( mockCerts, new Date('2020-07-20T05:00:00.000Z').valueOf(), @@ -55,13 +66,42 @@ describe('tls alert', () => { "agingCommonNameAndDate": "", "agingCount": 0, "count": 4, - "expiringCommonNameAndDate": "Common-One, expires: 2020-07-16T03:15:39.000Z; Common-Two, expires: 2020-07-18T03:15:39.000Z; Common-Three, expires: 2020-07-19T03:15:39.000Z", + "expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 900 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 901 days.; Common-Three, expires on 2020-07-19T03:15:39.000Z in 902 days.", "expiringCount": 3, + "hasAging": null, + "hasExpired": true, } `); }); it('sorts aging certs appropriate when creating summary', () => { + diffSpy + .mockReturnValueOnce(702) + .mockReturnValueOnce(701) + .mockReturnValueOnce(700); + const result = getCertSummary( + mockCerts, + new Date('2020-07-01T12:00:00.000Z').valueOf(), + new Date('2019-09-01T03:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 702 days ago.; Common-Three, valid since 2019-07-22T03:15:39.000Z, 701 days ago.; Common-One, valid since 2019-07-24T03:15:39.000Z, 700 days ago.", + "agingCount": 4, + "count": 4, + "expiringCommonNameAndDate": "", + "expiringCount": 0, + "hasAging": true, + "hasExpired": null, + } + `); + }); + + it('handles negative diff values appropriately for aging certs', () => { + diffSpy + .mockReturnValueOnce(700) + .mockReturnValueOnce(-90) + .mockReturnValueOnce(-80); const result = getCertSummary( mockCerts, new Date('2020-07-01T12:00:00.000Z').valueOf(), @@ -69,11 +109,36 @@ describe('tls alert', () => { ); expect(result).toMatchInlineSnapshot(` Object { - "agingCommonNameAndDate": "Common-Two, expires: 2020-07-18T03:15:39.000Z; Common-Three, expires: 2020-07-19T03:15:39.000Z; Common-One, expires: 2020-07-16T03:15:39.000Z", + "agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 700 days ago.; Common-Three, invalid until 2019-07-22T03:15:39.000Z, 90 days from now.; Common-One, invalid until 2019-07-24T03:15:39.000Z, 80 days from now.", "agingCount": 4, "count": 4, "expiringCommonNameAndDate": "", "expiringCount": 0, + "hasAging": true, + "hasExpired": null, + } + `); + }); + + it('handles negative diff values appropriately for expiring certs', () => { + diffSpy + .mockReturnValueOnce(-96) + .mockReturnValueOnce(-94) + .mockReturnValueOnce(2); + const result = getCertSummary( + mockCerts, + new Date('2020-07-20T05:00:00.000Z').valueOf(), + new Date('2019-03-01T00:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "agingCommonNameAndDate": "", + "agingCount": 0, + "count": 4, + "expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z 96 days ago; Common-Two, expired on 2020-07-18T03:15:39.000Z 94 days ago; Common-Three, expires on 2020-07-19T03:15:39.000Z in 2 days.", + "expiringCount": 3, + "hasAging": null, + "hasExpired": true, } `); }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 0ae0079cecc2c..cf8b7e2ef8370 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -23,19 +23,6 @@ const DEFAULT_TO = 'now'; const DEFAULT_INDEX = 0; const DEFAULT_SIZE = 20; -const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf(); - -const mapCertsToSummaryString = ( - certs: Cert[], - getCertValue: (cert: Cert) => string, - expirationVerb: 'expires' | 'valid after date', - maxSummaryItems: number -): string => - certs - .slice(0, maxSummaryItems) - .map(cert => `${cert.common_name}, ${expirationVerb}: ${getCertValue(cert)}`) - .reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), ''); - interface TlsAlertState { count: number; agingCount: number; @@ -46,9 +33,33 @@ interface TlsAlertState { hasExpired: true | null; } -const getValidAfter = (cert: Cert) => cert.certificate_not_valid_after ?? ''; +const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf(); + +const mapCertsToSummaryString = ( + certs: Cert[], + certLimitMessage: (cert: Cert) => string, + maxSummaryItems: number +): string => + certs + .slice(0, maxSummaryItems) + .map(cert => `${cert.common_name}, ${certLimitMessage(cert)}`) + .reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), ''); -const getValidBefore = (cert: Cert) => cert.certificate_not_valid_before ?? ''; +const getValidAfter = ({ certificate_not_valid_after: date }: Cert) => { + if (!date) return 'Error, missing `certificate_not_valid_after` date.'; + const relativeDate = moment().diff(date, 'days'); + return relativeDate >= 0 + ? `expires on ${date} in ${relativeDate} days.` + : `expired on ${date} ${Math.abs(relativeDate)} days ago`; +}; + +const getValidBefore = ({ certificate_not_valid_before: date }: Cert): string => { + if (!date) return 'Error, missing `certificate_not_valid_before` date.'; + const relativeDate = moment().diff(date, 'days'); + return relativeDate >= 0 + ? `valid since ${date}, ${relativeDate} days ago.` + : `invalid until ${date}, ${Math.abs(relativeDate)} days from now.`; +}; export const getCertSummary = ( certs: Cert[], @@ -73,18 +84,8 @@ export const getCertSummary = ( return { count: certs.length, agingCount: aging.length, - agingCommonNameAndDate: mapCertsToSummaryString( - aging, - getValidBefore, - 'valid after date', - maxSummaryItems - ), - expiringCommonNameAndDate: mapCertsToSummaryString( - expiring, - getValidAfter, - 'expires', - maxSummaryItems - ), + agingCommonNameAndDate: mapCertsToSummaryString(aging, getValidBefore, maxSummaryItems), + expiringCommonNameAndDate: mapCertsToSummaryString(expiring, getValidAfter, maxSummaryItems), expiringCount: expiring.length, hasAging: aging.length > 0 ? true : null, hasExpired: expiring.length > 0 ? true : null, From b13c74f745ce100bdd90ace8c6331c5b15d19255 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Apr 2020 17:06:46 -0400 Subject: [PATCH 26/49] Clean up more logic in certs request function. --- .../uptime/server/lib/requests/get_certs.ts | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 00ef78d26e697..1a8e5be55015d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -69,10 +69,11 @@ export const getCerts: UMElasticsearchQueryFn = async ({ }, }; - if (search || notValidBefore || notValidAfter) { - params.body.query.bool.should = []; - } if (search) { + if (!params.body.query.bool.should) { + params.body.query.bool.should = []; + } + params.body.query.bool.should = [ { wildcard: { @@ -105,24 +106,32 @@ export const getCerts: UMElasticsearchQueryFn = async ({ ]; } - if (notValidBefore) { - params.body.query.bool.should.push({ - range: { - 'tls.certificate_not_valid_before': { - lte: notValidBefore, - }, + if (notValidBefore || notValidAfter) { + const validityFilters: any = { + bool: { + should: [], }, - }); - } - - if (notValidAfter) { - params.body.query.bool.should.push({ - range: { - 'tls.certificate_not_valid_after': { - lte: notValidAfter, + }; + if (notValidBefore) { + validityFilters.bool.should.push({ + range: { + 'tls.certificate_not_valid_before': { + lte: notValidBefore, + }, }, - }, - }); + }); + } + if (notValidAfter) { + validityFilters.bool.should.push({ + range: { + 'tls.certificate_not_valid_after': { + lte: notValidAfter, + }, + }, + }); + } + + params.body.query.bool.filter.push(validityFilters); } const result = await callES('search', params); From 3092dad815c83ef4787b4342768076190530df7a Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 27 Apr 2020 17:17:26 -0400 Subject: [PATCH 27/49] Fix broken unit tests. --- .../plugins/uptime/public/lib/alert_types/translations.ts | 2 +- .../uptime/server/lib/alerts/__tests__/status_check.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts index abb72f7996fd2..621b203f37486 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/alert_types/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const MonitorStatusTranslations = { defaultActionMessage: i18n.translate('xpack.uptime.alerts.monitorStatus.defaultActionMessage', { - defaultMessage: '{contextMessage}\nLast triggered at:{lastTriggered}\n{downMonitors}', + defaultMessage: '{contextMessage}\nLast triggered at: {lastTriggered}\n{downMonitors}', values: { contextMessage: '{{context.message}}', lastTriggered: '{{state.lastTriggeredAt}}', diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 4f4c6e3011ad1..83e356aedd859 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { updateState } from '../common'; import { contextMessage, uniqueMonitorIds, - updateState, statusCheckAlertFactory, fullListByIdAndLocation, } from '../status_check'; From 1255978d1794e6bf793bb7ab9f414e59226a60f3 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 29 Apr 2020 12:15:39 -0400 Subject: [PATCH 28/49] Move tests for common function to new file. --- .../lib/alerts/__tests__/common.test.ts | 180 ++++++++++++++++++ .../lib/alerts/__tests__/status_check.test.ts | 174 ----------------- 2 files changed, 180 insertions(+), 174 deletions(-) create mode 100644 x-pack/plugins/uptime/server/lib/alerts/__tests__/common.test.ts diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/common.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/common.test.ts new file mode 100644 index 0000000000000..cd06370816ccc --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/common.test.ts @@ -0,0 +1,180 @@ +/* + * 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 { updateState } from '../common'; + +describe('updateState', () => { + let spy: jest.SpyInstance; + beforeEach(() => { + spy = jest.spyOn(Date.prototype, 'toISOString'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('sets initial state values', () => { + spy.mockImplementation(() => 'foo date string'); + const result = updateState({}, false); + expect(spy).toHaveBeenCalledTimes(1); + expect(result).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "foo date string", + "firstTriggeredAt": undefined, + "isTriggered": false, + "lastCheckedAt": "foo date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": undefined, + } + `); + }); + + it('updates the correct field in subsequent calls', () => { + spy + .mockImplementationOnce(() => 'first date string') + .mockImplementationOnce(() => 'second date string'); + const firstState = updateState({}, false); + const secondState = updateState(firstState, true); + expect(spy).toHaveBeenCalledTimes(2); + expect(firstState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": undefined, + "isTriggered": false, + "lastCheckedAt": "first date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": undefined, + } + `); + expect(secondState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "second date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": true, + "lastCheckedAt": "second date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "second date string", + } + `); + }); + + it('correctly marks resolution times', () => { + spy + .mockImplementationOnce(() => 'first date string') + .mockImplementationOnce(() => 'second date string') + .mockImplementationOnce(() => 'third date string'); + const firstState = updateState({}, true); + const secondState = updateState(firstState, true); + const thirdState = updateState(secondState, false); + expect(spy).toHaveBeenCalledTimes(3); + expect(firstState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "first date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "first date string", + "isTriggered": true, + "lastCheckedAt": "first date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "first date string", + } + `); + expect(secondState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "first date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "first date string", + "isTriggered": true, + "lastCheckedAt": "second date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "second date string", + } + `); + expect(thirdState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": "first date string", + "isTriggered": false, + "lastCheckedAt": "third date string", + "lastResolvedAt": "third date string", + "lastTriggeredAt": "second date string", + } + `); + }); + + it('correctly marks state fields across multiple triggers/resolutions', () => { + spy + .mockImplementationOnce(() => 'first date string') + .mockImplementationOnce(() => 'second date string') + .mockImplementationOnce(() => 'third date string') + .mockImplementationOnce(() => 'fourth date string') + .mockImplementationOnce(() => 'fifth date string'); + const firstState = updateState({}, false); + const secondState = updateState(firstState, true); + const thirdState = updateState(secondState, false); + const fourthState = updateState(thirdState, true); + const fifthState = updateState(fourthState, false); + expect(spy).toHaveBeenCalledTimes(5); + expect(firstState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": undefined, + "isTriggered": false, + "lastCheckedAt": "first date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": undefined, + } + `); + expect(secondState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "second date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": true, + "lastCheckedAt": "second date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "second date string", + } + `); + expect(thirdState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": false, + "lastCheckedAt": "third date string", + "lastResolvedAt": "third date string", + "lastTriggeredAt": "second date string", + } + `); + expect(fourthState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "fourth date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": true, + "lastCheckedAt": "fourth date string", + "lastResolvedAt": "third date string", + "lastTriggeredAt": "fourth date string", + } + `); + expect(fifthState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": false, + "lastCheckedAt": "fifth date string", + "lastResolvedAt": "fifth date string", + "lastTriggeredAt": "fourth date string", + } + `); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 83e356aedd859..2b0d90176bfe9 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { updateState } from '../common'; import { contextMessage, uniqueMonitorIds, @@ -337,179 +336,6 @@ describe('status check alert', () => { }); }); - describe('updateState', () => { - let spy: jest.SpyInstance; - beforeEach(() => { - spy = jest.spyOn(Date.prototype, 'toISOString'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('sets initial state values', () => { - spy.mockImplementation(() => 'foo date string'); - const result = updateState({}, false); - expect(spy).toHaveBeenCalledTimes(1); - expect(result).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "foo date string", - "firstTriggeredAt": undefined, - "isTriggered": false, - "lastCheckedAt": "foo date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": undefined, - } - `); - }); - - it('updates the correct field in subsequent calls', () => { - spy - .mockImplementationOnce(() => 'first date string') - .mockImplementationOnce(() => 'second date string'); - const firstState = updateState({}, false); - const secondState = updateState(firstState, true); - expect(spy).toHaveBeenCalledTimes(2); - expect(firstState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": undefined, - "isTriggered": false, - "lastCheckedAt": "first date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": undefined, - } - `); - expect(secondState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "second date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": true, - "lastCheckedAt": "second date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "second date string", - } - `); - }); - - it('correctly marks resolution times', () => { - spy - .mockImplementationOnce(() => 'first date string') - .mockImplementationOnce(() => 'second date string') - .mockImplementationOnce(() => 'third date string'); - const firstState = updateState({}, true); - const secondState = updateState(firstState, true); - const thirdState = updateState(secondState, false); - expect(spy).toHaveBeenCalledTimes(3); - expect(firstState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "first date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "first date string", - "isTriggered": true, - "lastCheckedAt": "first date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "first date string", - } - `); - expect(secondState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "first date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "first date string", - "isTriggered": true, - "lastCheckedAt": "second date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "second date string", - } - `); - expect(thirdState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": "first date string", - "isTriggered": false, - "lastCheckedAt": "third date string", - "lastResolvedAt": "third date string", - "lastTriggeredAt": "second date string", - } - `); - }); - - it('correctly marks state fields across multiple triggers/resolutions', () => { - spy - .mockImplementationOnce(() => 'first date string') - .mockImplementationOnce(() => 'second date string') - .mockImplementationOnce(() => 'third date string') - .mockImplementationOnce(() => 'fourth date string') - .mockImplementationOnce(() => 'fifth date string'); - const firstState = updateState({}, false); - const secondState = updateState(firstState, true); - const thirdState = updateState(secondState, false); - const fourthState = updateState(thirdState, true); - const fifthState = updateState(fourthState, false); - expect(spy).toHaveBeenCalledTimes(5); - expect(firstState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": undefined, - "isTriggered": false, - "lastCheckedAt": "first date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": undefined, - } - `); - expect(secondState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "second date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": true, - "lastCheckedAt": "second date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "second date string", - } - `); - expect(thirdState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": false, - "lastCheckedAt": "third date string", - "lastResolvedAt": "third date string", - "lastTriggeredAt": "second date string", - } - `); - expect(fourthState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "fourth date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": true, - "lastCheckedAt": "fourth date string", - "lastResolvedAt": "third date string", - "lastTriggeredAt": "fourth date string", - } - `); - expect(fifthState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": false, - "lastCheckedAt": "fifth date string", - "lastResolvedAt": "fifth date string", - "lastTriggeredAt": "fourth date string", - } - `); - }); - }); - describe('uniqueMonitorIds', () => { let items: GetMonitorStatusResult[]; beforeEach(() => { From 23264492f8128a193b36377989e030325ecfc8c0 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 29 Apr 2020 12:36:39 -0400 Subject: [PATCH 29/49] Fix logic error in test and add common state fields to tls alerts. --- .../uptime/server/lib/alerts/__tests__/tls.test.ts | 5 +++-- x-pack/plugins/uptime/server/lib/alerts/tls.ts | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts index 2a5bdbf475221..094aebfd50a4a 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts @@ -66,7 +66,7 @@ describe('tls alert', () => { "agingCommonNameAndDate": "", "agingCount": 0, "count": 4, - "expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 900 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 901 days.; Common-Three, expires on 2020-07-19T03:15:39.000Z in 902 days.", + "expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z 900 days ago; Common-Two, expired on 2020-07-18T03:15:39.000Z 901 days ago; Common-Three, expired on 2020-07-19T03:15:39.000Z 902 days ago", "expiringCount": 3, "hasAging": null, "hasExpired": true, @@ -122,6 +122,7 @@ describe('tls alert', () => { it('handles negative diff values appropriately for expiring certs', () => { diffSpy + // negative days are in the future, positive days are in the past .mockReturnValueOnce(-96) .mockReturnValueOnce(-94) .mockReturnValueOnce(2); @@ -135,7 +136,7 @@ describe('tls alert', () => { "agingCommonNameAndDate": "", "agingCount": 0, "count": 4, - "expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z 96 days ago; Common-Two, expired on 2020-07-18T03:15:39.000Z 94 days ago; Common-Three, expires on 2020-07-19T03:15:39.000Z in 2 days.", + "expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 96 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 94 days.; Common-Three, expired on 2020-07-19T03:15:39.000Z 2 days ago", "expiringCount": 3, "hasAging": null, "hasExpired": true, diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index cf8b7e2ef8370..3435f915dd470 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -15,6 +15,7 @@ import { DYNAMIC_SETTINGS_DEFAULTS, } from '../../../../../legacy/plugins/uptime/common/constants'; import { Cert } from '../../../../../legacy/plugins/uptime/common/runtime_types'; +import { commonStateTranslations } from './translations'; const { TLS } = ACTION_GROUP_DEFINITIONS; @@ -49,8 +50,8 @@ const getValidAfter = ({ certificate_not_valid_after: date }: Cert) => { if (!date) return 'Error, missing `certificate_not_valid_after` date.'; const relativeDate = moment().diff(date, 'days'); return relativeDate >= 0 - ? `expires on ${date} in ${relativeDate} days.` - : `expired on ${date} ${Math.abs(relativeDate)} days ago`; + ? `expired on ${date} ${relativeDate} days ago` + : `expires on ${date} in ${Math.abs(relativeDate)} days.`; }; const getValidBefore = ({ certificate_not_valid_before: date }: Cert): string => { @@ -137,7 +138,6 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ defaultMessage: 'The number of detected certs that are becoming too old.', }), }, - { name: 'agingCommonNameAndDate', description: i18n.translate( @@ -147,6 +147,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ } ), }, + ...commonStateTranslations, ], }, async executor(options) { @@ -186,6 +187,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ const alertInstance = alertInstanceFactory(TLS.id); const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); alertInstance.replaceState({ + ...updateState(state, certs.length > 0), ...summary, }); alertInstance.scheduleActions(TLS.id); From ba9bc8c12521c76cd67d69d54f439061b45b9580 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 29 Apr 2020 12:41:33 -0400 Subject: [PATCH 30/49] Extract common state field translations from status check alert. --- .../uptime/server/lib/alerts/common.ts | 1 - .../uptime/server/lib/alerts/status_check.ts | 68 +---------------- .../uptime/server/lib/alerts/translations.ts | 74 +++++++++++++++++++ 3 files changed, 76 insertions(+), 67 deletions(-) create mode 100644 x-pack/plugins/uptime/server/lib/alerts/translations.ts diff --git a/x-pack/plugins/uptime/server/lib/alerts/common.ts b/x-pack/plugins/uptime/server/lib/alerts/common.ts index 3bd40b0b6cd9d..4a726a25105ac 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/common.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/common.ts @@ -47,7 +47,6 @@ export const updateState: UpdateUptimeAlertState = (state, isTriggeredNow) => { } else if (isTriggeredNow) { cts = currentTriggerStarted; } - return { currentTriggerStarted: cts, firstCheckedAt: firstCheckedAt ?? now, diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index f6711dfc5144f..9ef8c0fce1094 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -15,6 +15,7 @@ import { GetMonitorStatusResult } from '../requests'; import { StatusCheckExecutorParamsType } from '../../../../../legacy/plugins/uptime/common/runtime_types'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; +import { commonStateTranslations } from './translations'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -168,72 +169,7 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = ), }, ], - state: [ - { - name: 'firstCheckedAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstCheckedAt', - { - defaultMessage: 'Timestamp indicating when this alert first checked', - } - ), - }, - { - name: 'firstTriggeredAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstTriggeredAt', - { - defaultMessage: 'Timestamp indicating when the alert first triggered', - } - ), - }, - { - name: 'currentTriggerStarted', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.currentTriggerStarted', - { - defaultMessage: - 'Timestamp indicating when the current trigger state began, if alert is triggered', - } - ), - }, - { - name: 'isTriggered', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.isTriggered', - { - defaultMessage: `Flag indicating if the alert is currently triggering`, - } - ), - }, - { - name: 'lastCheckedAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastCheckedAt', - { - defaultMessage: `Timestamp indicating the alert's most recent check time`, - } - ), - }, - { - name: 'lastResolvedAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastResolvedAt', - { - defaultMessage: `Timestamp indicating the most recent resolution time for this alert`, - } - ), - }, - { - name: 'lastTriggeredAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastTriggeredAt', - { - defaultMessage: `Timestamp indicating the alert's most recent trigger time`, - } - ), - }, - ], + state: [...commonStateTranslations], }, async executor(options: AlertExecutorOptions) { const { params: rawParams } = options; diff --git a/x-pack/plugins/uptime/server/lib/alerts/translations.ts b/x-pack/plugins/uptime/server/lib/alerts/translations.ts new file mode 100644 index 0000000000000..6de2a17d9df7d --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/translations.ts @@ -0,0 +1,74 @@ +/* + * 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 commonStateTranslations = [ + { + name: 'firstCheckedAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstCheckedAt', + { + defaultMessage: 'Timestamp indicating when this alert first checked', + } + ), + }, + { + name: 'firstTriggeredAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstTriggeredAt', + { + defaultMessage: 'Timestamp indicating when the alert first triggered', + } + ), + }, + { + name: 'currentTriggerStarted', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.currentTriggerStarted', + { + defaultMessage: + 'Timestamp indicating when the current trigger state began, if alert is triggered', + } + ), + }, + { + name: 'isTriggered', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.isTriggered', + { + defaultMessage: `Flag indicating if the alert is currently triggering`, + } + ), + }, + { + name: 'lastCheckedAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastCheckedAt', + { + defaultMessage: `Timestamp indicating the alert's most recent check time`, + } + ), + }, + { + name: 'lastResolvedAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastResolvedAt', + { + defaultMessage: `Timestamp indicating the most recent resolution time for this alert`, + } + ), + }, + { + name: 'lastTriggeredAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastTriggeredAt', + { + defaultMessage: `Timestamp indicating the alert's most recent trigger time`, + } + ), + }, +]; From 9162a65806a4e71df3993b3023a1faa62a814a64 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 29 Apr 2020 13:10:59 -0400 Subject: [PATCH 31/49] Add a comment. --- x-pack/plugins/uptime/server/lib/alerts/status_check.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index be5d6e4328f65..0eaa12e8f4372 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -32,7 +32,7 @@ export const uniqueMonitorIds = (items: GetMonitorStatusResult[]): Set = /** * Generates a message to include in contexts of alerts. * @param monitors the list of monitors to include in the message - * @param max + * @param max the maximum number of items the summary should contain */ export const contextMessage = (monitorIds: string[], max: number): string => { const MIN = 2; From c43691412e1f6d1c4753e33fa67d36ee3f1965b6 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 29 Apr 2020 15:51:18 -0400 Subject: [PATCH 32/49] Add nested context navigation for uptime alert selection. --- .../components/overview/alerts/alert_tls.tsx | 2 +- .../alerts/toggle_alert_flyout_button.tsx | 117 ++++++++++-------- .../overview/alerts/translations.ts | 15 +++ 3 files changed, 84 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx index 2633c12f74d3f..903db61a70ac6 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx @@ -14,7 +14,7 @@ import { } from '@elastic/eui'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { TlsTranslations } from './translations'; interface SettingsMessageExpressionPopoverProps { diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index 65cfc7ffeab09..a300279e06ca5 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -4,7 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiLink, + EuiPopover, +} from '@elastic/eui'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; @@ -15,9 +21,69 @@ interface Props { setAlertFlyoutVisible: (value: boolean | string) => void; } +const ALERT_CONTEXT_MAIN_PANEL_ID = 0; +const ALERT_CONTEXT_SELECT_TYPE_PANEL_ID = 1; + export const ToggleAlertFlyoutButtonComponent = ({ setAlertFlyoutVisible }: Props) => { const [isOpen, setIsOpen] = useState(false); const kibana = useKibana(); + const panels: EuiContextMenuPanelDescriptor[] = [ + { + id: ALERT_CONTEXT_MAIN_PANEL_ID, + title: 'main panel', + items: [ + { + 'aria-label': ToggleFlyoutTranslations.openAlertContextPanelAriaLabel, + 'data-test-subj': 'xpack.uptime.openAlertContextPanel', + name: ToggleFlyoutTranslations.openAlertContextPanelLabel, + icon: 'bell', + panel: 1, + }, + { + 'aria-label': ToggleFlyoutTranslations.navigateToAlertingUIAriaLabel, + 'data-test-subj': 'xpack.uptime.navigateToAlertingUi', + name: ( + + + + ), + icon: 'tableOfContents', + }, + ], + }, + { + id: ALERT_CONTEXT_SELECT_TYPE_PANEL_ID, + title: 'create alerts', + items: [ + { + 'aria-label': ToggleFlyoutTranslations.toggleMonitorStatusAriaLabel, + 'data-test-subj': 'xpack.uptime.toggleAlertFlyout', + name: ToggleFlyoutTranslations.toggleMonitorStatusContent, + onClick: () => { + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.MONITOR_STATUS); + setIsOpen(false); + }, + }, + { + 'aria-label': ToggleFlyoutTranslations.toggleTlsAriaLabel, + 'data-test-subj': 'xpack.uptime.toggleTlsAlertFlyout', + name: ToggleFlyoutTranslations.toggleTlsContent, + onClick: () => { + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.TLS); + setIsOpen(false); + }, + }, + ], + }, + ]; return ( - { - setAlertFlyoutVisible(CLIENT_ALERT_TYPES.MONITOR_STATUS); - setIsOpen(false); - }} - > - -
, - { - setAlertFlyoutVisible(CLIENT_ALERT_TYPES.TLS); - setIsOpen(false); - }} - > - - , - - - , - ]} - /> +
); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts index af26b206c3cd0..63ad43e2b9bc0 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts @@ -52,13 +52,28 @@ export const ToggleFlyoutTranslations = { toggleButtonAriaLabel: i18n.translate('xpack.uptime.alertsPopover.toggleButton.ariaLabel', { defaultMessage: 'Open alert context menu', }), + openAlertContextPanelAriaLabel: i18n.translate('xpack.uptime.openAlertContextPanel.ariaLabel', { + defaultMessage: 'Open the alert context panel so you can choose an alert type', + }), + openAlertContextPanelLabel: i18n.translate('xpack.uptime.openAlertContextPanel.label', { + defaultMessage: 'Create alert', + }), toggleTlsAriaLabel: i18n.translate('xpack.uptime.toggleTlsAlertButton.ariaLabel', { defaultMessage: 'Open TLS alert flyout', }), + toggleTlsContent: i18n.translate('xpack.uptime.toggleTlsAlertButton.content', { + defaultMessage: 'Create TLS alert', + }), toggleMonitorStatusAriaLabel: i18n.translate('xpack.uptime.toggleAlertFlyout.ariaLabel', { defaultMessage: 'Open add alert flyout', }), + toggleMonitorStatusContent: i18n.translate('xpack.uptime.toggleAlertButton.content', { + defaultMessage: 'Create Monitor Status alert', + }), navigateToAlertingUIAriaLabel: i18n.translate('xpack.uptime.navigateToAlertingUi', { defaultMessage: 'Leave Uptime and go to Alerting Management page', }), + navigateToAlertingButtonContent: i18n.translate('xpack.uptime.navigateToAlertingButton.content', { + defaultMessage: 'Manage alerts', + }), }; From 77ac31a2d62b60e4ecdbbcc7566b68cf751e6fc6 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 29 Apr 2020 18:42:38 -0400 Subject: [PATCH 33/49] Clean up types. --- x-pack/plugins/uptime/public/lib/alert_types/tls.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx index 07cb26bdbc653..0a5c09acbb69f 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx @@ -4,27 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PathReporter } from 'io-ts/lib/PathReporter'; import React from 'react'; -import { isRight } from 'fp-ts/lib/Either'; +import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { CLIENT_ALERT_TYPES } from '../../../common/constants'; import { TlsTranslations } from './translations'; -import { - AlertTypeModel, - ValidationResult, -} from '../../../../../../plugins/triggers_action_ui/public/types'; import { AlertTypeInitializer } from '.'; import { AlertTls } from '../../components/overview/alerts/alerts_containers/alert_tls'; const { name, defaultActionMessage } = TlsTranslations; -const validate = (): ValidationResult => ({}); - export const initTlsAlertType: AlertTypeInitializer = (): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.TLS, iconClass: 'uptimeApp', alertParamsExpression: () => , name, - validate, + validate: () => ({ errors: {} }), defaultActionMessage, }); From f044861b9538293fc13f456103fde0cbcb95c484 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 30 Apr 2020 13:56:09 -0400 Subject: [PATCH 34/49] Fix translation key typo. --- x-pack/plugins/uptime/server/lib/alerts/tls.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 7cfe04b6b1d47..1f997c3e7133e 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -92,7 +92,7 @@ export const getCertSummary = ( export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.tls', - name: i18n.translate('xpack/uptime.alerts.tls', { + name: i18n.translate('xpack.uptime.alerts.tls', { defaultMessage: 'Uptime TLS', }), validate: { From 9551b1444f2939f0b484185ab341719d5398db45 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 30 Apr 2020 14:04:29 -0400 Subject: [PATCH 35/49] Extract translations from tls alert factory. --- .../plugins/uptime/server/lib/alerts/tls.ts | 47 ++----------------- .../uptime/server/lib/alerts/translations.ts | 44 +++++++++++++++++ 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 1f997c3e7133e..fc78a91b00978 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -6,13 +6,12 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; -import { i18n } from '@kbn/i18n'; import { UptimeAlertTypeFactory } from './types'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { ACTION_GROUP_DEFINITIONS, DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { Cert } from '../../../common/runtime_types'; -import { commonStateTranslations } from './translations'; +import { commonStateTranslations, tlsTranslations } from './translations'; const { TLS } = ACTION_GROUP_DEFINITIONS; @@ -92,9 +91,7 @@ export const getCertSummary = ( export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.tls', - name: i18n.translate('xpack.uptime.alerts.tls', { - defaultMessage: 'Uptime TLS', - }), + name: tlsTranslations.alertFactoryName, validate: { params: schema.object({}), }, @@ -107,45 +104,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ ], actionVariables: { context: [], - state: [ - { - name: 'count', - description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.count', { - defaultMessage: 'The number of certs detected by the alert executor', - }), - }, - { - name: 'expiringCount', - description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.expiringCount', { - defaultMessage: 'The number of expiring certs detected by the alert.', - }), - }, - { - name: 'expiringCommonNameAndDate', - description: i18n.translate( - 'xpack.uptime.alerts.tls.actionVariables.state.expiringCommonNameAndDate', - { - defaultMessage: 'The common names and expiration date/time of the detected certs', - } - ), - }, - { - name: 'agingCount', - description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.agingCount', { - defaultMessage: 'The number of detected certs that are becoming too old.', - }), - }, - { - name: 'agingCommonNameAndDate', - description: i18n.translate( - 'xpack.uptime.alerts.tls.actionVariables.state.agingCommonNameAndDate', - { - defaultMessage: 'The common names and expiration date/time of the detected certs.', - } - ), - }, - ...commonStateTranslations, - ], + state: [...tlsTranslations.actionVariables, ...commonStateTranslations], }, async executor(options) { const { diff --git a/x-pack/plugins/uptime/server/lib/alerts/translations.ts b/x-pack/plugins/uptime/server/lib/alerts/translations.ts index 6de2a17d9df7d..9b8a031f8a927 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/translations.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/translations.ts @@ -72,3 +72,47 @@ export const commonStateTranslations = [ ), }, ]; + +export const tlsTranslations = { + alertFactoryName: i18n.translate('xpack.uptime.alerts.tls', { + defaultMessage: 'Uptime TLS', + }), + actionVariables: [ + { + name: 'count', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.count', { + defaultMessage: 'The number of certs detected by the alert executor', + }), + }, + { + name: 'expiringCount', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.expiringCount', { + defaultMessage: 'The number of expiring certs detected by the alert.', + }), + }, + { + name: 'expiringCommonNameAndDate', + description: i18n.translate( + 'xpack.uptime.alerts.tls.actionVariables.state.expiringCommonNameAndDate', + { + defaultMessage: 'The common names and expiration date/time of the detected certs', + } + ), + }, + { + name: 'agingCount', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.agingCount', { + defaultMessage: 'The number of detected certs that are becoming too old.', + }), + }, + { + name: 'agingCommonNameAndDate', + description: i18n.translate( + 'xpack.uptime.alerts.tls.actionVariables.state.agingCommonNameAndDate', + { + defaultMessage: 'The common names and expiration date/time of the detected certs.', + } + ), + }, + ], +}; From 4a395406d8eacb4670ad0b831ae4d62de05532b1 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 30 Apr 2020 14:22:50 -0400 Subject: [PATCH 36/49] Extract summary messages to translation file. --- .../plugins/uptime/server/lib/alerts/tls.ts | 8 ++--- .../uptime/server/lib/alerts/translations.ts | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index fc78a91b00978..034b68a0d359d 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -46,16 +46,16 @@ const getValidAfter = ({ certificate_not_valid_after: date }: Cert) => { if (!date) return 'Error, missing `certificate_not_valid_after` date.'; const relativeDate = moment().diff(date, 'days'); return relativeDate >= 0 - ? `expired on ${date} ${relativeDate} days ago` - : `expires on ${date} in ${Math.abs(relativeDate)} days.`; + ? tlsTranslations.validAfterExpiredString(date, relativeDate) + : tlsTranslations.validAfterExpiringString(date, Math.abs(relativeDate)); }; const getValidBefore = ({ certificate_not_valid_before: date }: Cert): string => { if (!date) return 'Error, missing `certificate_not_valid_before` date.'; const relativeDate = moment().diff(date, 'days'); return relativeDate >= 0 - ? `valid since ${date}, ${relativeDate} days ago.` - : `invalid until ${date}, ${Math.abs(relativeDate)} days from now.`; + ? tlsTranslations.validBeforeExpiredString(date, relativeDate) + : tlsTranslations.validBeforeExpiringString(date, Math.abs(relativeDate)); }; export const getCertSummary = ( diff --git a/x-pack/plugins/uptime/server/lib/alerts/translations.ts b/x-pack/plugins/uptime/server/lib/alerts/translations.ts index 9b8a031f8a927..f9e0cf63c083e 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/translations.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/translations.ts @@ -115,4 +115,36 @@ export const tlsTranslations = { ), }, ], + validAfterExpiredString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validAfterExpiredString', { + defaultMessage: `expired on {date} {relativeDate} days ago`, + values: { + date, + relativeDate, + }, + }), + validAfterExpiringString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validAfterExpiringString', { + defaultMessage: `expires on {date} in {relativeDate} days.`, + values: { + date, + relativeDate, + }, + }), + validBeforeExpiredString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validBeforeExpiredString', { + defaultMessage: 'valid since {date}, {relativeDate} days ago.', + values: { + date, + relativeDate, + }, + }), + validBeforeExpiringString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validBeforeExpiringString', { + defaultMessage: 'invalid until {date}, {relativeDate} days from now.', + values: { + date, + relativeDate, + }, + }), }; From cbf85eb3637d6db10cec303a098c127a08e5ccfb Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 30 Apr 2020 14:47:16 -0400 Subject: [PATCH 37/49] Change default tls alert time window from 1w to 1d. --- x-pack/plugins/uptime/server/lib/alerts/tls.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 034b68a0d359d..fcd52b1726cef 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -15,7 +15,7 @@ import { commonStateTranslations, tlsTranslations } from './translations'; const { TLS } = ACTION_GROUP_DEFINITIONS; -const DEFAULT_FROM = 'now-7d'; +const DEFAULT_FROM = 'now-1d'; const DEFAULT_TO = 'now'; const DEFAULT_INDEX = 0; const DEFAULT_SIZE = 20; From 7bc051ebf4194c944e7b1d70e0880524942b7e57 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 09:46:43 -0400 Subject: [PATCH 38/49] Remove unnecessary import. --- x-pack/test/api_integration/apis/uptime/rest/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index 0f7129ee6a0d7..2084c1a572058 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -44,9 +44,9 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dynamic_settings')); loadTestFile(require.resolve('./snapshot')); loadTestFile(require.resolve('./monitor_states_generated')); - loadTestFile(require.resolve('./snapshot')); loadTestFile(require.resolve('./telemetry_collectors')); }); + describe('with real-world data', () => { beforeEach('load heartbeat data', async () => await esArchiver.load('uptime/full_heartbeat')); afterEach('unload', async () => await esArchiver.unload('uptime/full_heartbeat')); From d693a2a4c98018bbb1876ac5b14fe5efc0825ea6 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 10:09:09 -0400 Subject: [PATCH 39/49] Simplify page linking. --- .../components/overview/alerts/alert_tls.tsx | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx index 903db61a70ac6..f11fb0a4b639f 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx @@ -4,18 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiExpression, - EuiFlexItem, - EuiFlexGroup, - EuiLink, - EuiPopover, - EuiSpacer, -} from '@elastic/eui'; +import { Link } from 'react-router-dom'; +import { EuiExpression, EuiFlexItem, EuiFlexGroup, EuiPopover, EuiSpacer } from '@elastic/eui'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { TlsTranslations } from './translations'; +import { SETTINGS_ROUTE } from '../../../../common/constants'; interface SettingsMessageExpressionPopoverProps { 'aria-label': string; @@ -33,8 +27,6 @@ const SettingsMessageExpressionPopover: React.FC { const [isOpen, setIsOpen] = useState(false); - const kibana = useKibana(); - const href = kibana.services?.application?.getUrlForApp('uptime#/settings'); return ( + { setAlertFlyoutVisible(false); @@ -72,7 +64,7 @@ const SettingsMessageExpressionPopover: React.FC settings page - + ), }} /> From 5d57988ede458fab4cacf1b19402f57024949555 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 10:33:23 -0400 Subject: [PATCH 40/49] Extract a non-trivial component to a dedicated file. --- .../components/overview/alerts/alert_tls.tsx | 69 +----------------- .../settings_message_expression_popover.tsx | 72 +++++++++++++++++++ 2 files changed, 75 insertions(+), 66 deletions(-) create mode 100644 x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx index f11fb0a4b639f..203ef07cc2bc8 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_tls.tsx @@ -4,73 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Link } from 'react-router-dom'; -import { EuiExpression, EuiFlexItem, EuiFlexGroup, EuiPopover, EuiSpacer } from '@elastic/eui'; -import React, { useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiExpression, EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import React from 'react'; import { TlsTranslations } from './translations'; -import { SETTINGS_ROUTE } from '../../../../common/constants'; - -interface SettingsMessageExpressionPopoverProps { - 'aria-label': string; - description: string; - id: string; - setAlertFlyoutVisible: (value: boolean) => void; - value: string; -} - -const SettingsMessageExpressionPopover: React.FC = ({ - 'aria-label': ariaLabel, - description, - setAlertFlyoutVisible, - value, - id, -}) => { - const [isOpen, setIsOpen] = useState(false); - return ( - setIsOpen(!isOpen)} - value={value} - /> - } - isOpen={isOpen} - closePopover={() => setIsOpen(false)} - > - - { - setAlertFlyoutVisible(false); - }} - onKeyUp={e => { - if (e.key === 'Enter') { - setAlertFlyoutVisible(false); - } - }} - > - settings page - - - ), - }} - /> - - ); -}; +import { SettingsMessageExpressionPopover } from './settings_message_expression_popover'; interface Props { ageThreshold?: number; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx new file mode 100644 index 0000000000000..8d9de08751eee --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx @@ -0,0 +1,72 @@ +/* + * 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 { EuiExpression, EuiPopover } from '@elastic/eui'; +import { Link } from 'react-router-dom'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { SETTINGS_ROUTE } from '../../../../common/constants'; + +interface SettingsMessageExpressionPopoverProps { + 'aria-label': string; + description: string; + id: string; + setAlertFlyoutVisible: (value: boolean) => void; + value: string; +} + +export const SettingsMessageExpressionPopover: React.FC = ({ + 'aria-label': ariaLabel, + description, + setAlertFlyoutVisible, + value, + id, +}) => { + const [isOpen, setIsOpen] = useState(false); + return ( + setIsOpen(!isOpen)} + value={value} + /> + } + isOpen={isOpen} + closePopover={() => setIsOpen(false)} + > + + { + setAlertFlyoutVisible(false); + }} + onKeyUp={e => { + if (e.key === 'Enter') { + setAlertFlyoutVisible(false); + } + }} + > + settings page + + + ), + }} + /> + + ); +}; From 2c07456e7de5b526f046345b7d15e573037c3027 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 10:42:32 -0400 Subject: [PATCH 41/49] Simplify create alert copy. --- .../uptime/public/components/overview/alerts/translations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts index 63ad43e2b9bc0..9bbb20333367c 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts @@ -62,13 +62,13 @@ export const ToggleFlyoutTranslations = { defaultMessage: 'Open TLS alert flyout', }), toggleTlsContent: i18n.translate('xpack.uptime.toggleTlsAlertButton.content', { - defaultMessage: 'Create TLS alert', + defaultMessage: 'TLS alert', }), toggleMonitorStatusAriaLabel: i18n.translate('xpack.uptime.toggleAlertFlyout.ariaLabel', { defaultMessage: 'Open add alert flyout', }), toggleMonitorStatusContent: i18n.translate('xpack.uptime.toggleAlertButton.content', { - defaultMessage: 'Create Monitor Status alert', + defaultMessage: 'Monitor Status alert', }), navigateToAlertingUIAriaLabel: i18n.translate('xpack.uptime.navigateToAlertingUi', { defaultMessage: 'Leave Uptime and go to Alerting Management page', From 6fb9d3b0f95db18e7d1c16c1d39b8f87edb4d5ce Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 10:59:16 -0400 Subject: [PATCH 42/49] Fix broken functional test. --- x-pack/test/functional/services/uptime/alerts.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/functional/services/uptime/alerts.ts b/x-pack/test/functional/services/uptime/alerts.ts index 3a8193ff3d327..03620621d68f1 100644 --- a/x-pack/test/functional/services/uptime/alerts.ts +++ b/x-pack/test/functional/services/uptime/alerts.ts @@ -13,6 +13,7 @@ export function UptimeAlertsProvider({ getService }: FtrProviderContext) { return { async openFlyout() { await testSubjects.click('xpack.uptime.alertsPopover.toggleButton', 5000); + await testSubjects.click('xpack.uptime.openAlertContextPanel', 5000); await testSubjects.click('xpack.uptime.toggleAlertFlyout', 5000); }, async openMonitorStatusAlertType(alertType: string) { From 5902515e944198524d79b1d60628e16b52fe4097 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 12:26:58 -0400 Subject: [PATCH 43/49] Fix busted types. --- .../server/lib/alerts/__tests__/tls.test.ts | 16 +++++----- .../plugins/uptime/server/lib/alerts/tls.ts | 32 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts index 3dac151457564..46e33a6a906c4 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts @@ -17,29 +17,29 @@ describe('tls alert', () => { diffSpy = jest.spyOn(moment.prototype, 'diff'); mockCerts = [ { - certificate_not_valid_after: '2020-07-16T03:15:39.000Z', - certificate_not_valid_before: '2019-07-24T03:15:39.000Z', + not_after: '2020-07-16T03:15:39.000Z', + not_before: '2019-07-24T03:15:39.000Z', common_name: 'Common-One', monitors: [{ name: 'monitor-one', id: 'monitor1' }], sha256: 'abc', }, { - certificate_not_valid_after: '2020-07-18T03:15:39.000Z', - certificate_not_valid_before: '2019-07-20T03:15:39.000Z', + not_after: '2020-07-18T03:15:39.000Z', + not_before: '2019-07-20T03:15:39.000Z', common_name: 'Common-Two', monitors: [{ name: 'monitor-two', id: 'monitor2' }], sha256: 'bcd', }, { - certificate_not_valid_after: '2020-07-19T03:15:39.000Z', - certificate_not_valid_before: '2019-07-22T03:15:39.000Z', + not_after: '2020-07-19T03:15:39.000Z', + not_before: '2019-07-22T03:15:39.000Z', common_name: 'Common-Three', monitors: [{ name: 'monitor-three', id: 'monitor3' }], sha256: 'cde', }, { - certificate_not_valid_after: '2020-07-25T03:15:39.000Z', - certificate_not_valid_before: '2019-07-25T03:15:39.000Z', + not_after: '2020-07-25T03:15:39.000Z', + not_before: '2019-07-25T03:15:39.000Z', common_name: 'Common-Four', monitors: [{ name: 'monitor-four', id: 'monitor4' }], sha256: 'def', diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index fcd52b1726cef..0d03438def0ba 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -10,7 +10,7 @@ import { UptimeAlertTypeFactory } from './types'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { ACTION_GROUP_DEFINITIONS, DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; -import { Cert } from '../../../common/runtime_types'; +import { Cert, CertResult } from '../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; const { TLS } = ACTION_GROUP_DEFINITIONS; @@ -42,7 +42,7 @@ const mapCertsToSummaryString = ( .map(cert => `${cert.common_name}, ${certLimitMessage(cert)}`) .reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), ''); -const getValidAfter = ({ certificate_not_valid_after: date }: Cert) => { +const getValidAfter = ({ not_after: date }: Cert) => { if (!date) return 'Error, missing `certificate_not_valid_after` date.'; const relativeDate = moment().diff(date, 'days'); return relativeDate >= 0 @@ -50,7 +50,7 @@ const getValidAfter = ({ certificate_not_valid_after: date }: Cert) => { : tlsTranslations.validAfterExpiringString(date, Math.abs(relativeDate)); }; -const getValidBefore = ({ certificate_not_valid_before: date }: Cert): string => { +const getValidBefore = ({ not_before: date }: Cert): string => { if (!date) return 'Error, missing `certificate_not_valid_before` date.'; const relativeDate = moment().diff(date, 'days'); return relativeDate >= 0 @@ -64,19 +64,13 @@ export const getCertSummary = ( ageThreshold: number, maxSummaryItems: number = 3 ): TlsAlertState => { - certs.sort((a, b) => - sortCerts(a.certificate_not_valid_after ?? '', b.certificate_not_valid_after ?? '') - ); + certs.sort((a, b) => sortCerts(a.not_after ?? '', b.not_after ?? '')); const expiring = certs.filter( - cert => new Date(cert.certificate_not_valid_after ?? '').valueOf() < expirationThreshold + cert => new Date(cert.not_after ?? '').valueOf() < expirationThreshold ); - certs.sort((a, b) => - sortCerts(a.certificate_not_valid_before ?? '', b.certificate_not_valid_before ?? '') - ); - const aging = certs.filter( - cert => new Date(cert.certificate_not_valid_before ?? '').valueOf() < ageThreshold - ); + certs.sort((a, b) => sortCerts(a.not_before ?? '', b.not_before ?? '')); + const aging = certs.filter(cert => new Date(cert.not_before ?? '').valueOf() < ageThreshold); return { count: certs.length, @@ -113,7 +107,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ } = options; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); - const certs = await libs.requests.getCerts({ + const { certs, total }: CertResult = await libs.requests.getCerts({ callES: callCluster, dynamicSettings, from: DEFAULT_FROM, @@ -124,9 +118,13 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.expiration}d`, notValidBefore: `now-${dynamicSettings.certThresholds?.age ?? DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.age}d`, + sortBy: '@timestamp', + direction: 'desc', }); - if (certs.length) { + const foundCerts = total > 0; + + if (foundCerts) { const absoluteExpirationThreshold = moment() .add( dynamicSettings.certThresholds?.expiration ?? @@ -143,12 +141,12 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ const alertInstance = alertInstanceFactory(TLS.id); const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); alertInstance.replaceState({ - ...updateState(state, certs.length > 0), + ...updateState(state, foundCerts), ...summary, }); alertInstance.scheduleActions(TLS.id); } - return updateState(state, certs.length > 0); + return updateState(state, foundCerts); }, }); From 190610c072956d63860670b715f46d0561dc74fe Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 13:38:28 -0400 Subject: [PATCH 44/49] Fix tls query error. --- x-pack/plugins/uptime/server/lib/alerts/tls.ts | 2 +- x-pack/plugins/uptime/server/lib/requests/get_certs.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 0d03438def0ba..518e3ed93b424 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -118,7 +118,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.expiration}d`, notValidBefore: `now-${dynamicSettings.certThresholds?.age ?? DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.age}d`, - sortBy: '@timestamp', + sortBy: 'common_name', direction: 'desc', }); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index d3f97a49b23ab..57a59936ddf7c 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -144,6 +144,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn params.body.query.bool.filter.push(validityFilters); } + // console.log(JSON.stringify(params, null, 2)); const result = await callES('search', params); const certs = (result?.hits?.hits ?? []).map((hit: any) => { From 86add5b09d9b9a4a11fa37039241793a5c4d5ddf Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 16:17:10 -0400 Subject: [PATCH 45/49] Allow for alerts toggle button to receive a set of types to display. --- .../alerts/alerts_containers/index.ts | 5 +- .../toggle_alert_flyout_button.tsx | 7 +- .../alerts/toggle_alert_flyout_button.tsx | 150 +++++++++++------- 3 files changed, 103 insertions(+), 59 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts index 87179a96fc0b2..c30312b19f711 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/index.ts @@ -5,5 +5,8 @@ */ export { AlertMonitorStatus } from './alert_monitor_status'; -export { ToggleAlertFlyoutButton } from './toggle_alert_flyout_button'; +export { + ToggleAlertFlyoutButton, + ToggleAlertFlyoutButtonProps, +} from './toggle_alert_flyout_button'; export { UptimeAlertsFlyoutWrapper } from './uptime_alerts_flyout_wrapper'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx index 68684e9a62f42..c6b2074338c63 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/toggle_alert_flyout_button.tsx @@ -9,10 +9,15 @@ import { useDispatch } from 'react-redux'; import { setAlertFlyoutType, setAlertFlyoutVisible } from '../../../../state/actions'; import { ToggleAlertFlyoutButtonComponent } from '../index'; -export const ToggleAlertFlyoutButton = () => { +export interface ToggleAlertFlyoutButtonProps { + alertOptions?: string[]; +} + +export const ToggleAlertFlyoutButton: React.FC = props => { const dispatch = useDispatch(); return ( { if (typeof value === 'string') { dispatch(setAlertFlyoutType(value)); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index a300279e06ca5..cba96cd2fe5b1 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -8,6 +8,7 @@ import { EuiButtonEmpty, EuiContextMenu, EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, EuiLink, EuiPopover, } from '@elastic/eui'; @@ -16,74 +17,109 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { CLIENT_ALERT_TYPES } from '../../../../common/constants'; import { ToggleFlyoutTranslations } from './translations'; +import { ToggleAlertFlyoutButtonProps } from './alerts_containers'; -interface Props { +interface ComponentProps { setAlertFlyoutVisible: (value: boolean | string) => void; } +type Props = ComponentProps & ToggleAlertFlyoutButtonProps; + const ALERT_CONTEXT_MAIN_PANEL_ID = 0; const ALERT_CONTEXT_SELECT_TYPE_PANEL_ID = 1; -export const ToggleAlertFlyoutButtonComponent = ({ setAlertFlyoutVisible }: Props) => { +export const ToggleAlertFlyoutButtonComponent: React.FC = ({ + alertOptions, + setAlertFlyoutVisible, +}) => { const [isOpen, setIsOpen] = useState(false); const kibana = useKibana(); - const panels: EuiContextMenuPanelDescriptor[] = [ - { - id: ALERT_CONTEXT_MAIN_PANEL_ID, - title: 'main panel', - items: [ - { - 'aria-label': ToggleFlyoutTranslations.openAlertContextPanelAriaLabel, - 'data-test-subj': 'xpack.uptime.openAlertContextPanel', - name: ToggleFlyoutTranslations.openAlertContextPanelLabel, - icon: 'bell', - panel: 1, - }, - { - 'aria-label': ToggleFlyoutTranslations.navigateToAlertingUIAriaLabel, - 'data-test-subj': 'xpack.uptime.navigateToAlertingUi', - name: ( - - - - ), - icon: 'tableOfContents', - }, - ], + const monitorStatusAlertContextMenuItem: EuiContextMenuPanelItemDescriptor = { + 'aria-label': ToggleFlyoutTranslations.toggleMonitorStatusAriaLabel, + 'data-test-subj': 'xpack.uptime.toggleAlertFlyout', + name: ToggleFlyoutTranslations.toggleMonitorStatusContent, + onClick: () => { + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.MONITOR_STATUS); + setIsOpen(false); }, - { - id: ALERT_CONTEXT_SELECT_TYPE_PANEL_ID, - title: 'create alerts', - items: [ - { - 'aria-label': ToggleFlyoutTranslations.toggleMonitorStatusAriaLabel, - 'data-test-subj': 'xpack.uptime.toggleAlertFlyout', - name: ToggleFlyoutTranslations.toggleMonitorStatusContent, - onClick: () => { - setAlertFlyoutVisible(CLIENT_ALERT_TYPES.MONITOR_STATUS); - setIsOpen(false); - }, - }, - { - 'aria-label': ToggleFlyoutTranslations.toggleTlsAriaLabel, - 'data-test-subj': 'xpack.uptime.toggleTlsAlertFlyout', - name: ToggleFlyoutTranslations.toggleTlsContent, - onClick: () => { - setAlertFlyoutVisible(CLIENT_ALERT_TYPES.TLS); - setIsOpen(false); - }, - }, - ], + }; + + const tlsAlertContextMenuItem: EuiContextMenuPanelItemDescriptor = { + 'aria-label': ToggleFlyoutTranslations.toggleTlsAriaLabel, + 'data-test-subj': 'xpack.uptime.toggleTlsAlertFlyout', + name: ToggleFlyoutTranslations.toggleTlsContent, + onClick: () => { + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.TLS); + setIsOpen(false); }, - ]; + }; + + const managementContextItem: EuiContextMenuPanelItemDescriptor = { + 'aria-label': ToggleFlyoutTranslations.navigateToAlertingUIAriaLabel, + 'data-test-subj': 'xpack.uptime.navigateToAlertingUi', + name: ( + + + + ), + icon: 'tableOfContents', + }; + + let selectionItems: EuiContextMenuPanelItemDescriptor[] = []; + if (!alertOptions) { + selectionItems = [monitorStatusAlertContextMenuItem, tlsAlertContextMenuItem]; + } else { + alertOptions.forEach(option => { + if (option === CLIENT_ALERT_TYPES.MONITOR_STATUS) + selectionItems.push(monitorStatusAlertContextMenuItem); + else if (option === CLIENT_ALERT_TYPES.TLS) selectionItems.push(tlsAlertContextMenuItem); + }); + } + + if (selectionItems.length === 1) { + selectionItems[0].icon = 'bell'; + } + + let panels: EuiContextMenuPanelDescriptor[]; + if (selectionItems.length === 1) { + panels = [ + { + id: ALERT_CONTEXT_MAIN_PANEL_ID, + title: 'main panel', + items: [...selectionItems, managementContextItem], + }, + ]; + } else { + panels = [ + { + id: ALERT_CONTEXT_MAIN_PANEL_ID, + title: 'main panel', + items: [ + { + 'aria-label': ToggleFlyoutTranslations.openAlertContextPanelAriaLabel, + 'data-test-subj': 'xpack.uptime.openAlertContextPanel', + name: ToggleFlyoutTranslations.openAlertContextPanelLabel, + icon: 'bell', + panel: ALERT_CONTEXT_SELECT_TYPE_PANEL_ID, + }, + managementContextItem, + ], + }, + { + id: ALERT_CONTEXT_SELECT_TYPE_PANEL_ID, + title: 'create alerts', + items: selectionItems, + }, + ]; + } return ( Date: Mon, 4 May 2020 16:18:01 -0400 Subject: [PATCH 46/49] Add alerts toggle button to certs page. --- x-pack/plugins/uptime/public/pages/certificates.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/public/pages/certificates.tsx b/x-pack/plugins/uptime/public/pages/certificates.tsx index d6c1b8e2b4568..92a41adcf01cd 100644 --- a/x-pack/plugins/uptime/public/pages/certificates.tsx +++ b/x-pack/plugins/uptime/public/pages/certificates.tsx @@ -19,13 +19,14 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useTrackPageview } from '../../../observability/public'; import { PageHeader } from './page_header'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; -import { OVERVIEW_ROUTE, SETTINGS_ROUTE } from '../../common/constants'; +import { OVERVIEW_ROUTE, SETTINGS_ROUTE, CLIENT_ALERT_TYPES } from '../../common/constants'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; import { UptimeRefreshContext } from '../contexts'; import * as labels from './translations'; import { UptimePage, useUptimeTelemetry } from '../hooks'; import { certificatesSelector, getCertificatesAction } from '../state/certificates/certificates'; import { CertificateList, CertificateSearch, CertSort } from '../components/certificates'; +import { ToggleAlertFlyoutButton } from '../components/overview/alerts/alerts_containers'; const DEFAULT_PAGE_SIZE = 10; const LOCAL_STORAGE_KEY = 'xpack.uptime.certList.pageSize'; @@ -83,6 +84,9 @@ export const CertificatesPage: React.FC = () => { + + + From f3f18c1ba6168ec16449a907cdd1ec1c304c71e3 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 17:23:18 -0400 Subject: [PATCH 47/49] Fix copy. --- .../uptime/public/components/overview/alerts/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts index 9bbb20333367c..406654c808186 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts @@ -68,7 +68,7 @@ export const ToggleFlyoutTranslations = { defaultMessage: 'Open add alert flyout', }), toggleMonitorStatusContent: i18n.translate('xpack.uptime.toggleAlertButton.content', { - defaultMessage: 'Monitor Status alert', + defaultMessage: 'Monitor status alert', }), navigateToAlertingUIAriaLabel: i18n.translate('xpack.uptime.navigateToAlertingUi', { defaultMessage: 'Leave Uptime and go to Alerting Management page', From 98418d29f29d6016967a4ba104cf0b821bfa8135 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 17:41:06 -0400 Subject: [PATCH 48/49] Fixup punctuation in default message to avoid double-period symbols. --- x-pack/plugins/uptime/public/lib/alert_types/translations.ts | 4 ++-- x-pack/plugins/uptime/server/lib/alerts/translations.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/uptime/public/lib/alert_types/translations.ts b/x-pack/plugins/uptime/public/lib/alert_types/translations.ts index 621b203f37486..cdf3cd107b00f 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/translations.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/translations.ts @@ -26,12 +26,12 @@ export const TlsTranslations = { {expiringConditionalOpen} Expiring cert count: {expiringCount} -Expiring Certificates: {expiringCommonNameAndDate}. +Expiring Certificates: {expiringCommonNameAndDate} {expiringConditionalClose} {agingConditionalOpen} Aging cert count: {agingCount} -Aging Certificates: {agingCommonNameAndDate}. +Aging Certificates: {agingCommonNameAndDate} {agingConditionalClose} `, values: { diff --git a/x-pack/plugins/uptime/server/lib/alerts/translations.ts b/x-pack/plugins/uptime/server/lib/alerts/translations.ts index f9e0cf63c083e..e41930aad5af0 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/translations.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/translations.ts @@ -117,7 +117,7 @@ export const tlsTranslations = { ], validAfterExpiredString: (date: string, relativeDate: number) => i18n.translate('xpack.uptime.alerts.tls.validAfterExpiredString', { - defaultMessage: `expired on {date} {relativeDate} days ago`, + defaultMessage: `expired on {date} {relativeDate} days ago.`, values: { date, relativeDate, From 8566b0094cccf33e0215f3cacb90d7060bf699d3 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 4 May 2020 17:50:09 -0400 Subject: [PATCH 49/49] Refresh snapshots. --- x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts index 46e33a6a906c4..2b74d608417cc 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts @@ -66,7 +66,7 @@ describe('tls alert', () => { "agingCommonNameAndDate": "", "agingCount": 0, "count": 4, - "expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z 900 days ago; Common-Two, expired on 2020-07-18T03:15:39.000Z 901 days ago; Common-Three, expired on 2020-07-19T03:15:39.000Z 902 days ago", + "expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z 900 days ago.; Common-Two, expired on 2020-07-18T03:15:39.000Z 901 days ago.; Common-Three, expired on 2020-07-19T03:15:39.000Z 902 days ago.", "expiringCount": 3, "hasAging": null, "hasExpired": true, @@ -136,7 +136,7 @@ describe('tls alert', () => { "agingCommonNameAndDate": "", "agingCount": 0, "count": 4, - "expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 96 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 94 days.; Common-Three, expired on 2020-07-19T03:15:39.000Z 2 days ago", + "expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 96 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 94 days.; Common-Three, expired on 2020-07-19T03:15:39.000Z 2 days ago.", "expiringCount": 3, "hasAging": null, "hasExpired": true,