diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts b/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts index b615c1d914ac1..6cef41347bbf6 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor_management/locations.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isLeft } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; const LocationGeoCodec = t.interface({ @@ -30,6 +31,9 @@ export const ServiceLocationCodec = t.interface({ export const ServiceLocationsCodec = t.array(ServiceLocationCodec); +export const isServiceLocationInvalid = (location: ServiceLocation) => + isLeft(ServiceLocationCodec.decode(location)); + export const ServiceLocationsApiResponseCodec = t.interface({ locations: ServiceLocationsCodec, }); diff --git a/x-pack/plugins/uptime/common/types/index.ts b/x-pack/plugins/uptime/common/types/index.ts index dd448cb2706b5..c8bd99a2a4802 100644 --- a/x-pack/plugins/uptime/common/types/index.ts +++ b/x-pack/plugins/uptime/common/types/index.ts @@ -5,31 +5,6 @@ * 2.0. */ -import { SimpleSavedObject } from 'kibana/public'; -import { SyntheticsMonitor } from '../runtime_types'; - -/** Represents the average monitor duration ms at a point in time. */ -export interface MonitorDurationAveragePoint { - /** The timeseries value for this point. */ - x: number; - /** The average duration ms for the monitor. */ - y?: number | null; -} - -export interface LocationDurationLine { - name: string; - - line: MonitorDurationAveragePoint[]; -} - -/** The data used to populate the monitor charts. */ -export interface MonitorDurationResult { - /** The average values for the monitor duration. */ - locationDurationLines: LocationDurationLine[]; -} - -export interface MonitorIdParam { - monitorId: string; -} - -export type SyntheticsMonitorSavedObject = SimpleSavedObject; +export * from './monitor_duration'; +export * from './synthetics_monitor'; +export * from './monitor_validation'; diff --git a/x-pack/plugins/uptime/common/types/monitor_duration.ts b/x-pack/plugins/uptime/common/types/monitor_duration.ts new file mode 100644 index 0000000000000..253adba03cdcf --- /dev/null +++ b/x-pack/plugins/uptime/common/types/monitor_duration.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** Represents the average monitor duration ms at a point in time. */ +export interface MonitorDurationAveragePoint { + /** The timeseries value for this point. */ + x: number; + /** The average duration ms for the monitor. */ + y?: number | null; +} + +export interface LocationDurationLine { + name: string; + + line: MonitorDurationAveragePoint[]; +} + +/** The data used to populate the monitor charts. */ +export interface MonitorDurationResult { + /** The average values for the monitor duration. */ + locationDurationLines: LocationDurationLine[]; +} diff --git a/x-pack/plugins/uptime/common/types/monitor_validation.ts b/x-pack/plugins/uptime/common/types/monitor_validation.ts new file mode 100644 index 0000000000000..25d581fcf0558 --- /dev/null +++ b/x-pack/plugins/uptime/common/types/monitor_validation.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConfigKey, MonitorFields } from '../runtime_types'; + +export type Validator = (config: Partial) => boolean; + +export type Validation = Partial>; diff --git a/x-pack/plugins/uptime/common/types/synthetics_monitor.ts b/x-pack/plugins/uptime/common/types/synthetics_monitor.ts new file mode 100644 index 0000000000000..4045aa952506b --- /dev/null +++ b/x-pack/plugins/uptime/common/types/synthetics_monitor.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SimpleSavedObject } from 'kibana/public'; +import { SyntheticsMonitor } from '../runtime_types'; + +export interface MonitorIdParam { + monitorId: string; +} + +export type SyntheticsMonitorSavedObject = SimpleSavedObject; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/types.tsx b/x-pack/plugins/uptime/public/components/fleet_package/types.tsx index de35076a6a850..d3df3a1926887 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/types.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/types.tsx @@ -12,13 +12,13 @@ import { ConfigKey, ContentType, DataStream, - MonitorFields, Mode, ThrottlingConfigKey, ThrottlingSuffix, ThrottlingSuffixType, } from '../../../common/runtime_types'; export * from '../../../common/runtime_types/monitor_management'; +export * from '../../../common/types/monitor_validation'; export interface PolicyConfig { [DataStream.HTTP]: HTTPFields; @@ -27,10 +27,6 @@ export interface PolicyConfig { [DataStream.BROWSER]: BrowserFields; } -export type Validator = (config: Partial) => boolean; - -export type Validation = Partial>; - export const contentTypesToMode = { [ContentType.FORM]: Mode.FORM, [ContentType.JSON]: Mode.JSON, diff --git a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx index 453d7eac44c5f..adc2a0a8ed344 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx @@ -10,7 +10,12 @@ import { screen, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render } from '../../../lib/helper/rtl_helpers'; import * as fetchers from '../../../state/api/monitor_management'; -import { DataStream, HTTPFields, ScheduleUnit, SyntheticsMonitor } from '../../fleet_package/types'; +import { + DataStream, + HTTPFields, + ScheduleUnit, + SyntheticsMonitor, +} from '../../../../common/runtime_types'; import { ActionBar } from './action_bar'; describe('', () => { diff --git a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx index bb6d7d041b9c7..7cc3608a8bc1d 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useCallback, useContext, useState, useEffect } from 'react'; import { useParams, Redirect } from 'react-router-dom'; import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -14,9 +14,10 @@ import { FETCH_STATUS, useFetcher } from '../../../../../observability/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { MONITOR_MANAGEMENT } from '../../../../common/constants'; +import { UptimeSettingsContext } from '../../../contexts'; import { setMonitor } from '../../../state/api'; -import { SyntheticsMonitor } from '../../fleet_package/types'; +import { SyntheticsMonitor } from '../../../../common/runtime_types'; interface Props { monitor: SyntheticsMonitor; @@ -26,6 +27,7 @@ interface Props { export const ActionBar = ({ monitor, isValid, onSave }: Props) => { const { monitorId } = useParams<{ monitorId: string }>(); + const { basePath } = useContext(UptimeSettingsContext); const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false); const [isSaving, setIsSaving] = useState(false); @@ -87,7 +89,12 @@ export const ActionBar = ({ monitor, isValid, onSave }: Props) => { - + {DISCARD_LABEL} @@ -101,7 +108,7 @@ export const ActionBar = ({ monitor, isValid, onSave }: Props) => { isLoading={isSaving} disabled={hasBeenSubmitted && !isValid} > - {monitorId ? EDIT_MONITOR_LABEL : SAVE_MONITOR_LABEL} + {monitorId ? UPDATE_MONITOR_LABEL : SAVE_MONITOR_LABEL} @@ -119,8 +126,8 @@ const SAVE_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.saveMo defaultMessage: 'Save monitor', }); -const EDIT_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.editMonitorLabel', { - defaultMessage: 'Edit monitor', +const UPDATE_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.updateMonitorLabel', { + defaultMessage: 'Update monitor', }); const VALIDATION_ERROR_LABEL = i18n.translate('xpack.uptime.monitorManagement.validationError', { diff --git a/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx b/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx index 87bb2fe032492..fb9d2302b5b35 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx @@ -6,15 +6,10 @@ */ import React, { useMemo } from 'react'; -import { - ConfigKey, - MonitorFields, - TLSFields, - PolicyConfig, - DataStream, -} from '../fleet_package/types'; +import { ConfigKey, MonitorFields, TLSFields, DataStream } from '../../../common/runtime_types'; import { useTrackPageview } from '../../../../observability/public'; import { SyntheticsProviders } from '../fleet_package/contexts'; +import { PolicyConfig } from '../fleet_package/types'; import { MonitorConfig } from './monitor_config/monitor_config'; interface Props { diff --git a/x-pack/plugins/uptime/public/components/monitor_management/hooks/use_format_monitor.ts b/x-pack/plugins/uptime/public/components/monitor_management/hooks/use_format_monitor.ts index 952c4c31da59b..49d467d7b8799 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/hooks/use_format_monitor.ts +++ b/x-pack/plugins/uptime/public/components/monitor_management/hooks/use_format_monitor.ts @@ -4,8 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { useEffect, useRef, useState } from 'react'; -import { ConfigKey, DataStream, Validation, MonitorFields } from '../../fleet_package/types'; +import { ConfigKey, DataStream, MonitorFields } from '../../../../common/runtime_types'; +import { Validation } from '../../../../common/types'; interface Props { monitorType: DataStream; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx index 8a617722cfcf6..11caf092c93c7 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.test.tsx @@ -47,14 +47,20 @@ describe('', () => { }); it('renders locations', () => { - render(, { state }); + render( + , + { state } + ); expect(screen.getByText(LOCATIONS_LABEL)).toBeInTheDocument(); expect(screen.queryByText('US Central')).not.toBeInTheDocument(); }); it('shows location options when clicked', async () => { - render(, { state }); + render( + , + { state } + ); userEvent.click(screen.getByRole('button')); @@ -62,7 +68,10 @@ describe('', () => { }); it('prevents bad inputs', async () => { - render(, { state }); + render( + , + { state } + ); userEvent.click(screen.getByRole('button')); userEvent.type(screen.getByRole('textbox'), 'fake location'); @@ -75,11 +84,23 @@ describe('', () => { }); it('calls setLocations', async () => { - render(, { state }); + render( + , + { state } + ); userEvent.click(screen.getByRole('button')); userEvent.click(screen.getByText('US Central')); expect(setLocations).toBeCalledWith([location]); }); + + it('shows invalid error', async () => { + render( + , + { state } + ); + + expect(screen.getByText('At least one service location must be specified')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx index c22a74bdbbbe8..2d261e169299a 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/locations.tsx @@ -10,15 +10,15 @@ import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import { monitorManagementListSelector } from '../../../state/selectors'; -import { ServiceLocation } from '../../../../common/runtime_types/monitor_management'; +import { ServiceLocation } from '../../../../common/runtime_types'; interface Props { selectedLocations: ServiceLocation[]; setLocations: React.Dispatch>; + isInvalid: boolean; } -export const ServiceLocations = ({ selectedLocations, setLocations }: Props) => { - const [locationsInputRef, setLocationsInputRef] = useState(null); +export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid }: Props) => { const [error, setError] = useState(null); const { locations } = useSelector(monitorManagementListSelector); @@ -30,23 +30,25 @@ export const ServiceLocations = ({ selectedLocations, setLocations }: Props) => }; const onSearchChange = (value: string, hasMatchingOptions?: boolean) => { - setError(value.length === 0 || hasMatchingOptions ? null : `"${value}" is not a valid option`); + setError(value.length === 0 || hasMatchingOptions ? null : getInvalidOptionError(value)); }; - const onBlur = () => { - if (locationsInputRef) { - const { value } = locationsInputRef; - setError(value.length === 0 ? null : `"${value}" is not a valid option`); + const onBlur = (event: unknown) => { + const inputElement = (event as FocusEvent)?.target as HTMLInputElement; + if (inputElement) { + const { value } = inputElement; + setError(value.length === 0 ? null : getInvalidOptionError(value)); } }; + const errorMessage = error ?? (isInvalid ? VALIDATION_ERROR : null); + return ( - + const PLACEHOLDER_LABEL = i18n.translate( 'xpack.uptime.monitorManagement.serviceLocationsPlaceholderLabel', { - defaultMessage: 'Select one or locations to run your monitor.', + defaultMessage: 'Select one or more locations to run your monitor.', + } +); + +const VALIDATION_ERROR = i18n.translate( + 'xpack.uptime.monitorManagement.serviceLocationsValidationError', + { + defaultMessage: 'At least one service location must be specified', } ); +const getInvalidOptionError = (value: string) => + i18n.translate('xpack.uptime.monitorManagement.serviceLocationsOptionError', { + defaultMessage: '"{value}" is not a valid option', + values: { + value, + }, + }); + export const LOCATIONS_LABEL = i18n.translate( 'xpack.uptime.monitorManagement.monitorLocationsLabel', { diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx index 4ba3b07cae0e4..bff4fc7d57470 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { defaultConfig, usePolicyConfigContext } from '../../fleet_package/contexts'; import { usePolicy } from '../../fleet_package/hooks/use_policy'; -import { validate } from '../../fleet_package/validation'; +import { validate } from '../validation'; import { ActionBar } from '../action_bar/action_bar'; import { useFormatMonitor } from '../hooks/use_format_monitor'; import { MonitorFields } from './monitor_fields'; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx index 9edcc6e4c5430..dca6490903ef7 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { EuiForm } from '@elastic/eui'; -import { DataStream } from '../../fleet_package/types'; +import { DataStream } from '../../../../common/runtime_types'; import { usePolicyConfigContext } from '../../fleet_package/contexts'; import { CustomFields } from '../../fleet_package/custom_fields'; -import { validate } from '../../fleet_package/validation'; +import { validate } from '../validation'; import { MonitorNameAndLocation } from './monitor_name_location'; export const MonitorFields = () => { @@ -22,7 +22,7 @@ export const MonitorFields = () => { validate={validate[monitorType]} dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP, DataStream.BROWSER]} > - + ); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx index b9bd1b164b0b8..f3e04a0040418 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx @@ -6,25 +6,57 @@ */ import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { ConfigKey } from '../../../../common/runtime_types'; +import { Validation } from '../../../../common/types'; import { usePolicyConfigContext } from '../../fleet_package/contexts'; import { ServiceLocations } from './locations'; -export const MonitorNameAndLocation = () => { +interface Props { + validate: Validation; +} + +export const MonitorNameAndLocation = ({ validate }: Props) => { const { name, setName, locations = [], setLocations } = usePolicyConfigContext(); + const isNameInvalid = !!validate[ConfigKey.NAME]?.({ [ConfigKey.NAME]: name }); + const isLocationsInvalid = !!validate[ConfigKey.LOCATIONS]?.({ + [ConfigKey.LOCATIONS]: locations, + }); + return ( <> - + + } + fullWidth={true} + isInvalid={isNameInvalid} + error={ + + } + > setName(event.target.value)} /> - + ); }; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.test.tsx index 8884eb197b99b..d352ccef51a94 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render } from '../../../lib/helper/rtl_helpers'; -import { DataStream, HTTPFields, ScheduleUnit } from '../../fleet_package/types'; +import { DataStream, HTTPFields, ScheduleUnit } from '../../../../common/runtime_types'; import { MonitorManagementList } from './monitor_list'; import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/validation.test.ts b/x-pack/plugins/uptime/public/components/monitor_management/validation.test.ts new file mode 100644 index 0000000000000..ffb815a65acca --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/validation.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ConfigKey, + DataStream, + HTTPFields, + MonitorFields, + ScheduleUnit, + ServiceLocations, +} from '../../../common/runtime_types'; +import { validate } from './validation'; + +describe('[Monitor Management] validation', () => { + const commonPropsValid: Partial = { + [ConfigKey.SCHEDULE]: { number: '5', unit: ScheduleUnit.MINUTES }, + [ConfigKey.TIMEOUT]: '3m', + [ConfigKey.LOCATIONS]: [ + { + id: 'test-service-location', + url: 'https:test-url.com', + geo: { lat: 33.33432323, lon: 73.23424221 }, + label: 'EU West', + }, + ] as ServiceLocations, + }; + + describe('HTTP', () => { + const httpPropsValid: Partial = { + ...commonPropsValid, + [ConfigKey.RESPONSE_STATUS_CHECK]: ['200', '204'], + [ConfigKey.RESPONSE_HEADERS_CHECK]: { 'Content-Type': 'application/json' }, + [ConfigKey.REQUEST_HEADERS_CHECK]: { 'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8' }, + [ConfigKey.MAX_REDIRECTS]: '3', + [ConfigKey.URLS]: 'https:// example-url.com', + }; + + it('should return false for all valid props', () => { + const validators = validate[DataStream.HTTP]; + const keysToValidate = [ + ConfigKey.SCHEDULE, + ConfigKey.TIMEOUT, + ConfigKey.LOCATIONS, + ConfigKey.RESPONSE_STATUS_CHECK, + ConfigKey.RESPONSE_HEADERS_CHECK, + ConfigKey.REQUEST_HEADERS_CHECK, + ConfigKey.MAX_REDIRECTS, + ConfigKey.URLS, + ]; + const validatorFns = keysToValidate.map((key) => validators[key]); + const result = validatorFns.map((fn) => fn?.(httpPropsValid) ?? true); + + expect(result).not.toEqual(expect.arrayContaining([true])); + }); + + it('should invalidate when locations is empty', () => { + const validators = validate[DataStream.HTTP]; + const validatorFn = validators[ConfigKey.LOCATIONS]; + const result = [undefined, null, []].map( + (testValue) => + validatorFn?.({ [ConfigKey.LOCATIONS]: testValue } as Partial) ?? false + ); + + expect(result).toEqual([true, true, true]); + }); + }); + + // TODO: Add test for other monitor types if needed +}); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/validation.ts b/x-pack/plugins/uptime/public/components/monitor_management/validation.ts new file mode 100644 index 0000000000000..2b028295be88f --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/validation.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + ConfigKey, + DataStream, + ScheduleUnit, + MonitorFields, + isServiceLocationInvalid, +} from '../../../common/runtime_types'; +import { Validation, Validator } from '../../../common/types'; + +export const digitsOnly = /^[0-9]*$/g; +export const includesValidPort = /[^\:]+:[0-9]{1,5}$/g; + +type ValidationLibrary = Record; + +// returns true if invalid +function validateHeaders(headers: T): boolean { + return Object.keys(headers).some((key) => { + if (key) { + const whiteSpaceRegEx = /[\s]/g; + return whiteSpaceRegEx.test(key); + } else { + return false; + } + }); +} + +// returns true if invalid +const validateTimeout = ({ + scheduleNumber, + scheduleUnit, + timeout, +}: { + scheduleNumber: string; + scheduleUnit: ScheduleUnit; + timeout: string; +}): boolean => { + let schedule: number; + switch (scheduleUnit) { + case ScheduleUnit.SECONDS: + schedule = parseFloat(scheduleNumber); + break; + case ScheduleUnit.MINUTES: + schedule = parseFloat(scheduleNumber) * 60; + break; + default: + schedule = parseFloat(scheduleNumber); + } + + return parseFloat(timeout) > schedule; +}; + +// validation functions return true when invalid +const validateCommon: ValidationLibrary = { + [ConfigKey.NAME]: ({ [ConfigKey.NAME]: value }) => { + return !value || typeof value !== 'string'; + }, + [ConfigKey.SCHEDULE]: ({ [ConfigKey.SCHEDULE]: value }) => { + const { number, unit } = value as MonitorFields[ConfigKey.SCHEDULE]; + const parsedFloat = parseFloat(number); + return !parsedFloat || !unit || parsedFloat < 1; + }, + [ConfigKey.TIMEOUT]: ({ [ConfigKey.TIMEOUT]: timeout, [ConfigKey.SCHEDULE]: schedule }) => { + const { number, unit } = schedule as MonitorFields[ConfigKey.SCHEDULE]; + + return ( + !timeout || + parseFloat(timeout) < 0 || + validateTimeout({ + timeout, + scheduleNumber: number, + scheduleUnit: unit, + }) + ); + }, + [ConfigKey.LOCATIONS]: ({ [ConfigKey.LOCATIONS]: locations }) => { + return ( + !Array.isArray(locations) || locations.length < 1 || locations.some(isServiceLocationInvalid) + ); + }, +}; + +const validateHTTP: ValidationLibrary = { + [ConfigKey.RESPONSE_STATUS_CHECK]: ({ [ConfigKey.RESPONSE_STATUS_CHECK]: value }) => { + const statusCodes = value as MonitorFields[ConfigKey.RESPONSE_STATUS_CHECK]; + return statusCodes.length ? statusCodes.some((code) => !`${code}`.match(digitsOnly)) : false; + }, + [ConfigKey.RESPONSE_HEADERS_CHECK]: ({ [ConfigKey.RESPONSE_HEADERS_CHECK]: value }) => { + const headers = value as MonitorFields[ConfigKey.RESPONSE_HEADERS_CHECK]; + return validateHeaders(headers); + }, + [ConfigKey.REQUEST_HEADERS_CHECK]: ({ [ConfigKey.REQUEST_HEADERS_CHECK]: value }) => { + const headers = value as MonitorFields[ConfigKey.REQUEST_HEADERS_CHECK]; + return validateHeaders(headers); + }, + [ConfigKey.MAX_REDIRECTS]: ({ [ConfigKey.MAX_REDIRECTS]: value }) => + (!!value && !`${value}`.match(digitsOnly)) || + parseFloat(value as MonitorFields[ConfigKey.MAX_REDIRECTS]) < 0, + [ConfigKey.URLS]: ({ [ConfigKey.URLS]: value }) => !value, + ...validateCommon, +}; + +const validateTCP: Record = { + [ConfigKey.HOSTS]: ({ [ConfigKey.HOSTS]: value }) => { + return !value || !`${value}`.match(includesValidPort); + }, + ...validateCommon, +}; + +const validateICMP: ValidationLibrary = { + [ConfigKey.HOSTS]: ({ [ConfigKey.HOSTS]: value }) => !value, + [ConfigKey.WAIT]: ({ [ConfigKey.WAIT]: value }) => + !!value && + !digitsOnly.test(`${value}`) && + parseFloat(value as MonitorFields[ConfigKey.WAIT]) < 0, + ...validateCommon, +}; + +const validateThrottleValue = (speed: string | undefined, allowZero?: boolean) => { + if (speed === undefined || speed === '') return false; + const throttleValue = parseFloat(speed); + return isNaN(throttleValue) || (allowZero ? throttleValue < 0 : throttleValue <= 0); +}; + +const validateBrowser: ValidationLibrary = { + ...validateCommon, + [ConfigKey.SOURCE_ZIP_URL]: ({ + [ConfigKey.SOURCE_ZIP_URL]: zipUrl, + [ConfigKey.SOURCE_INLINE]: inlineScript, + }) => !zipUrl && !inlineScript, + [ConfigKey.SOURCE_INLINE]: ({ + [ConfigKey.SOURCE_ZIP_URL]: zipUrl, + [ConfigKey.SOURCE_INLINE]: inlineScript, + }) => !zipUrl && !inlineScript, + [ConfigKey.DOWNLOAD_SPEED]: ({ [ConfigKey.DOWNLOAD_SPEED]: downloadSpeed }) => + validateThrottleValue(downloadSpeed), + [ConfigKey.UPLOAD_SPEED]: ({ [ConfigKey.UPLOAD_SPEED]: uploadSpeed }) => + validateThrottleValue(uploadSpeed), + [ConfigKey.LATENCY]: ({ [ConfigKey.LATENCY]: latency }) => validateThrottleValue(latency, true), +}; + +export type ValidateDictionary = Record; + +export const validate: ValidateDictionary = { + [DataStream.HTTP]: validateHTTP, + [DataStream.TCP]: validateTCP, + [DataStream.ICMP]: validateICMP, + [DataStream.BROWSER]: validateBrowser, +};