From 4686f3544ca17e7db12bdbeb022dd7362f05bb0d Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 25 Sep 2024 00:25:25 +1000 Subject: [PATCH] [8.x] [Synthetics] Public API's refactor (#190531) (#193863) # Backport This will backport the following commits from `main` to `8.x`: - [[Synthetics] Public API's refactor (#190531)](https://github.com/elastic/kibana/pull/190531) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Shahzad --- .../monitor_management/monitor_types.ts | 16 +- .../monitor_types_project.ts | 6 +- .../synthetics/e2e/config.ts | 1 + .../project_monitor_read_only.journey.ts | 67 +++++--- .../journeys/services/add_monitor_project.ts | 26 +-- .../journeys/services/synthetics_services.ts | 7 +- .../components/monitor_details_panel.tsx | 3 +- .../common/header/action_menu_content.tsx | 17 +- .../monitor_add_edit/form/defaults.tsx | 21 ++- .../monitor_add_edit/form/form_config.tsx | 1 + .../monitor_add_edit/form/validation.tsx | 3 +- .../hooks/use_clone_monitor.ts | 4 +- .../hooks/use_monitor_not_found.tsx | 4 +- .../monitor_details_portal.tsx | 30 +++- .../monitor_edit_page.test.tsx | 7 + .../monitor_add_edit/monitor_edit_page.tsx | 33 ++-- .../hooks/use_selected_monitor.tsx | 5 +- .../overview/monitor_detail_flyout.test.tsx | 67 ++++---- .../overview/monitor_detail_flyout.tsx | 33 ++-- .../state/monitor_details/actions.ts | 13 +- .../synthetics/state/monitor_details/api.ts | 8 +- .../synthetics/state/monitor_details/index.ts | 11 +- .../state/monitor_details/selectors.ts | 11 ++ .../synthetics/state/monitor_list/actions.ts | 3 +- .../apps/synthetics/state/monitor_list/api.ts | 2 +- .../synthetics/state/monitor_list/effects.ts | 8 +- .../state/monitor_management/api.ts | 17 +- .../synthetics/server/queries/get_monitor.ts | 19 +-- .../routes/monitor_cruds/add_monitor.ts | 11 +- .../add_monitor/add_monitor_api.ts | 4 +- .../routes/monitor_cruds/add_monitor/utils.ts | 6 +- .../routes/monitor_cruds/edit_monitor.ts | 24 ++- .../saved_object_to_monitor.test.ts} | 105 ++++++++++-- .../formatters/saved_object_to_monitor.ts | 158 ++++++++++++++++++ .../routes/monitor_cruds/get_monitor.ts | 50 +++--- .../routes/monitor_cruds/get_monitors_list.ts | 8 +- .../server/routes/monitor_cruds/helper.ts | 97 ----------- .../normalizers/common_fields.test.ts | 2 + .../normalizers/common_fields.ts | 5 +- .../normalizers/http_monitor.ts | 2 +- .../apis/synthetics/add_monitor.ts | 31 +++- .../add_monitor_private_location.ts | 14 +- .../apis/synthetics/add_monitor_project.ts | 62 ++++--- .../apis/synthetics/add_monitor_public_api.ts | 18 +- .../apis/synthetics/edit_monitor.ts | 36 ++-- .../synthetics/edit_monitor_public_api.ts | 5 +- .../synthetics/fixtures/browser_monitor.json | 3 +- .../apis/synthetics/get_monitor.ts | 56 +++++-- .../apis/synthetics/helper/monitor.ts | 18 +- .../synthetics_monitor_test_service.ts | 51 +++++- 50 files changed, 767 insertions(+), 442 deletions(-) rename x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/{helper.test.ts => formatters/saved_object_to_monitor.test.ts} (64%) create mode 100644 x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/formatters/saved_object_to_monitor.ts delete mode 100644 x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/helper.ts diff --git a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types.ts index 7246ba2d9bac6..578d823aafae2 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -173,7 +173,8 @@ export type ICMPFields = t.TypeOf; export const HTTPSimpleFieldsCodec = t.intersection([ t.interface({ [ConfigKey.METADATA]: MetadataCodec, - [ConfigKey.MAX_REDIRECTS]: t.string, + // string is for yaml config and number for public api + [ConfigKey.MAX_REDIRECTS]: t.union([t.string, t.number]), [ConfigKey.URLS]: getNonEmptyStringCodec('url'), [ConfigKey.PORT]: t.union([t.number, t.null]), }), @@ -318,6 +319,11 @@ export const MonitorFieldsCodec = t.intersection([ BrowserFieldsCodec, ]); +export const MonitorFieldsResultCodec = t.intersection([ + MonitorFieldsCodec, + t.interface({ id: t.string, updated_at: t.string, created_at: t.string }), +]); + // Monitor, represents one of (Icmp | Tcp | Http | Browser) decrypted export const SyntheticsMonitorCodec = t.union([ HTTPFieldsCodec, @@ -336,7 +342,7 @@ export const EncryptedSyntheticsMonitorCodec = t.union([ export const SyntheticsMonitorWithIdCodec = t.intersection([ SyntheticsMonitorCodec, - t.interface({ id: t.string }), + t.interface({ id: t.string, updated_at: t.string, created_at: t.string }), ]); const HeartbeatFieldsCodec = t.intersection([ @@ -355,7 +361,10 @@ const HeartbeatFieldsCodec = t.intersection([ ]); export const HeartbeatConfigCodec = t.intersection([ - SyntheticsMonitorWithIdCodec, + SyntheticsMonitorCodec, + t.interface({ + id: t.string, + }), t.partial({ fields_under_root: t.boolean, fields: HeartbeatFieldsCodec, @@ -400,6 +409,7 @@ export type BrowserFields = t.TypeOf; export type BrowserSimpleFields = t.TypeOf; export type BrowserAdvancedFields = t.TypeOf; export type MonitorFields = t.TypeOf; +export type MonitorFieldsResult = t.TypeOf; export type HeartbeatFields = t.TypeOf; export type SyntheticsMonitor = t.TypeOf; export type SyntheticsMonitorWithId = t.TypeOf; diff --git a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts index 8e25ec1a714e4..ed3e6b28eef26 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/runtime_types/monitor_management/monitor_types_project.ts @@ -62,7 +62,7 @@ export const ProjectMonitorCodec = t.intersection([ hash: t.string, namespace: t.string, retestOnFailure: t.boolean, - labels: t.record(t.string, t.string), + fields: t.record(t.string, t.string), }), ]); @@ -91,14 +91,10 @@ export const ProjectMonitorsResponseCodec = t.intersection([ }), ]); -export type ProjectMonitorThrottlingConfig = t.TypeOf; - export type ProjectMonitor = t.TypeOf; export type LegacyProjectMonitorsRequest = t.TypeOf; export type ProjectMonitorsRequest = t.TypeOf; -export type ProjectMonitorsResponse = t.TypeOf; - export type ProjectMonitorMetaData = t.TypeOf; diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/config.ts b/x-pack/plugins/observability_solution/synthetics/e2e/config.ts index 4b2aef573d687..8e1e97e5c1d37 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/config.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/config.ts @@ -10,6 +10,7 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import { get } from 'lodash'; import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; + import { readKibanaConfig } from './tasks/read_kibana_config'; const MANIFEST_KEY = 'xpack.uptime.service.manifestUrl'; const SERVICE_PASSWORD = 'xpack.uptime.service.password'; diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/project_monitor_read_only.journey.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/project_monitor_read_only.journey.ts index 3e2a4467bd61a..edef44a21d051 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/project_monitor_read_only.journey.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/project_monitor_read_only.journey.ts @@ -6,9 +6,10 @@ */ import { after, before, expect, journey, step } from '@elastic/synthetics'; +import { omit } from 'lodash'; import { SyntheticsMonitor } from '@kbn/synthetics-plugin/common/runtime_types'; import { SyntheticsServices } from './services/synthetics_services'; -import { cleanTestMonitors, enableMonitorManagedViaApi } from './services/add_monitor'; +import { cleanTestMonitors } from './services/add_monitor'; import { addTestMonitorProject } from './services/add_monitor_project'; import { syntheticsAppPageProvider } from '../page_objects/synthetics_app'; @@ -23,14 +24,13 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => { before(async () => { await cleanTestMonitors(params); - await enableMonitorManagedViaApi(params.kibanaUrl); + }); + step('Go to monitor-management', async () => { await addTestMonitorProject(params.kibanaUrl, monitorName); await syntheticsApp.waitForLoadingToFinish(); - }); - step('Go to monitor-management', async () => { await syntheticsApp.navigateToMonitorManagement(); }); @@ -62,11 +62,16 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => { // hash is always reset to empty string when monitor is edited // this ensures that when the monitor is pushed again, the monitor // config in the process takes precedence - expect(newConfiguration).toEqual({ - ...originalMonitorConfiguration, - hash: '', - revision: 2, - }); + expect(omit(newConfiguration, ['updated_at'])).toEqual( + omit( + { + ...originalMonitorConfiguration, + hash: '', + revision: 2, + }, + ['updated_at'] + ) + ); }); step('Navigate to edit monitor', async () => { @@ -83,29 +88,39 @@ journey('ProjectMonitorReadOnly', async ({ page, params }) => { // hash is always reset to empty string when monitor is edited // this ensures that when the monitor is pushed again, the monitor // config in the process takes precedence - expect(newConfiguration).toEqual({ - ...originalMonitorConfiguration, - hash: '', - revision: 3, - alert: { - status: { - enabled: !(originalMonitorConfiguration?.alert?.status?.enabled as boolean), - }, - tls: { - enabled: originalMonitorConfiguration?.alert?.tls?.enabled as boolean, + expect(omit(newConfiguration, ['updated_at'])).toEqual( + omit( + { + ...originalMonitorConfiguration, + hash: '', + revision: 3, + alert: { + status: { + enabled: !(originalMonitorConfiguration?.alert?.status?.enabled as boolean), + }, + tls: { + enabled: originalMonitorConfiguration?.alert?.tls?.enabled as boolean, + }, + }, + enabled: !originalMonitorConfiguration?.enabled, }, - }, - enabled: !originalMonitorConfiguration?.enabled, - }); + ['updated_at'] + ) + ); }); step('Monitor can be re-pushed and overwrite any changes', async () => { await addTestMonitorProject(params.kibanaUrl, monitorName); const repushedConfiguration = await services.getMonitor(monitorId); - expect(repushedConfiguration).toEqual({ - ...originalMonitorConfiguration, - revision: 4, - }); + expect(omit(repushedConfiguration, ['updated_at'])).toEqual( + omit( + { + ...originalMonitorConfiguration, + revision: 4, + }, + ['updated_at'] + ) + ); }); step('Navigate to edit monitor', async () => { diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor_project.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor_project.ts index 8cc9529e94c9f..85c0f512beb1b 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor_project.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/add_monitor_project.ts @@ -17,23 +17,15 @@ export const addTestMonitorProject = async ( const testData = { ...testProjectMonitorBrowser(name, config), }; - try { - return await axios.put( - kibanaUrl + - SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace( - '{projectName}', - projectName - ), - testData, - { - auth: { username: 'elastic', password: 'changeme' }, - headers: { 'kbn-xsrf': 'true', 'x-elastic-internal-origin': 'synthetics-e2e' }, - } - ); - } catch (e) { - // eslint-disable-next-line no-console - console.log(e); - } + return await axios.put( + kibanaUrl + + SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', projectName), + testData, + { + auth: { username: 'elastic', password: 'changeme' }, + headers: { 'kbn-xsrf': 'true', 'x-elastic-internal-origin': 'synthetics-e2e' }, + } + ); }; const testProjectMonitorBrowser = (name: string, config?: Record) => ({ diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts index 988327ee3fc19..028f8a736e93c 100644 --- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts +++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/services/synthetics_services.ts @@ -28,10 +28,9 @@ export class SyntheticsServices { try { const { data } = await this.requester.request({ description: 'get monitor by id', - path: SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId), - query: { - decrypted: true, - }, + path: + SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId) + + '?internal=true', method: 'GET', }); return data as SyntheticsMonitor; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx index d0db68035b147..1222455443bbf 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/monitor_details_panel.tsx @@ -28,6 +28,7 @@ import { EncryptedSyntheticsSavedMonitor, MonitorFields, Ping, + SyntheticsMonitorWithId, } from '../../../../../../common/runtime_types'; import { MonitorTypeBadge } from './monitor_type_badge'; import { useDateFormat } from '../../../../../hooks/use_date_format'; @@ -36,7 +37,7 @@ export interface MonitorDetailsPanelProps { latestPing?: Ping; loading: boolean; configId: string; - monitor: EncryptedSyntheticsSavedMonitor | null; + monitor: SyntheticsMonitorWithId | EncryptedSyntheticsSavedMonitor | null; hideEnabled?: boolean; hideLocations?: boolean; hasBorder?: boolean; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx index bc878a4388f02..84e536bfec0ee 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx @@ -15,7 +15,12 @@ import { LastRefreshed } from '../components/last_refreshed'; import { AutoRefreshButton } from '../components/auto_refresh_button'; import { useSyntheticsSettingsContext } from '../../../contexts'; import { useGetUrlParams } from '../../../hooks'; -import { MONITOR_ROUTE, SETTINGS_ROUTE } from '../../../../../../common/constants'; +import { + MONITOR_ADD_ROUTE, + MONITOR_EDIT_ROUTE, + MONITOR_ROUTE, + SETTINGS_ROUTE, +} from '../../../../../../common/constants'; import { stringifyUrlParams } from '../../../utils/url_params'; import { InspectorHeaderLink } from './inspector_header_link'; import { ToggleAlertFlyoutButton } from '../../alerts/toggle_alert_flyout_button'; @@ -41,6 +46,8 @@ export function ActionMenuContent(): React.ReactElement { }; /* useSelector(monitorStatusSelector) TODO: Implement state for monitor status */ const detailRouteMatch = useRouteMatch(MONITOR_ROUTE); + const isEditRoute = useRouteMatch(MONITOR_EDIT_ROUTE); + const isAddRoute = useRouteMatch(MONITOR_ADD_ROUTE); const monitorId = selectedMonitor?.monitor?.id; const syntheticExploratoryViewLink = createExploratoryViewUrl( @@ -69,8 +76,12 @@ export function ActionMenuContent(): React.ReactElement { return ( - - + {!isEditRoute && !isAddRoute && ( + <> + + + + )} { schedule.number = `${schedule.number}s`; } + const params = monitorWithFormMonitorType[ConfigKey.PARAMS]; + if (typeof params !== 'string' && params) { + try { + monitorWithFormMonitorType[ConfigKey.PARAMS] = JSON.stringify(params); + } catch (e) { + // ignore + } + } + const browserMonitor = monitor as BrowserFields; + + const pwOptions = browserMonitor[ConfigKey.PLAYWRIGHT_OPTIONS]; + if (typeof pwOptions !== 'string' && pwOptions) { + try { + (monitorWithFormMonitorType as BrowserFields)[ConfigKey.PLAYWRIGHT_OPTIONS] = + JSON.stringify(pwOptions); + } catch (e) { + // ignore + } + } + // handle default monitor types from Uptime, which don't contain `ConfigKey.FORM_MONITOR_TYPE` if (!formMonitorType) { formMonitorType = @@ -81,7 +101,6 @@ export const formatDefaultFormValues = (monitor?: SyntheticsMonitor) => { switch (formMonitorType) { case FormMonitorType.MULTISTEP: - const browserMonitor = monitor as BrowserFields; return { ...monitorWithFormMonitorType, 'source.inline': { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx index 7e29504aca41f..470c344deb5e0 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx @@ -148,6 +148,7 @@ export const BROWSER_ADVANCED = (readOnly: boolean) => [ FIELD(readOnly)[ConfigKey.IGNORE_HTTPS_ERRORS], FIELD(readOnly)[ConfigKey.SYNTHETICS_ARGS], FIELD(readOnly)[ConfigKey.PLAYWRIGHT_OPTIONS], + FIELD(readOnly)[ConfigKey.PARAMS], ], }, ]; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx index fbd792fd02238..3cd741fc44ce7 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/form/validation.tsx @@ -117,8 +117,7 @@ const validateHTTP: ValidationLibrary = { return validateHeaders(headers); }, [ConfigKey.MAX_REDIRECTS]: ({ [ConfigKey.MAX_REDIRECTS]: value }) => - (!!value && !`${value}`.match(DIGITS_ONLY)) || - parseFloat(value as MonitorFields[ConfigKey.MAX_REDIRECTS]) < 0, + (!!value && !`${value}`.match(DIGITS_ONLY)) || parseFloat(value as string) < 0, [ConfigKey.URLS]: ({ [ConfigKey.URLS]: value }) => !value, ...validateCommon, }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_clone_monitor.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_clone_monitor.ts index ff871ae5fad98..637166af20155 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_clone_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_clone_monitor.ts @@ -6,13 +6,13 @@ */ import { useFetcher } from '@kbn/observability-shared-plugin/public'; +import { fetchSyntheticsMonitor } from '../../../state/monitor_details/api'; import { useGetUrlParams } from '../../../hooks'; -import { getDecryptedMonitorAPI } from '../../../state/monitor_management/api'; export const useCloneMonitor = () => { const { cloneId } = useGetUrlParams(); return useFetcher(() => { if (!cloneId) return Promise.resolve(undefined); - return getDecryptedMonitorAPI({ id: cloneId }); + return fetchSyntheticsMonitor({ monitorId: cloneId }); }, [cloneId]); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_not_found.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_not_found.tsx index 6577b039e2290..6a27d43808f84 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_not_found.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_not_found.tsx @@ -6,10 +6,10 @@ */ import { useEffect } from 'react'; -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; import { useGetUrlParams, useUrlParams } from '../../../hooks'; +import { IHttpSerializedFetchError } from '../../../state'; -export const useMonitorNotFound = (error?: IHttpFetchError, id?: string) => { +export const useMonitorNotFound = (error?: IHttpSerializedFetchError | null, id?: string) => { const { packagePolicyId } = useGetUrlParams(); const updateUrlParams = useUrlParams()[1]; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_details_portal.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_details_portal.tsx index fbdd823b49682..d63106bb42a7f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_details_portal.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_details_portal.tsx @@ -22,17 +22,21 @@ interface Props { export const MonitorDetailsLinkPortal = ({ name, configId, locationId, updateUrl }: Props) => { return ( - + {locationId ? ( + + ) : ( + + )} ); }; -export const MonitorDetailsLink = ({ name, configId, locationId, updateUrl }: Props) => { +const MonitorDetailsLinkWithLocation = ({ name, configId, locationId, updateUrl }: Props) => { const selectedLocation = useSelectedLocation(updateUrl); let locId = locationId; @@ -45,6 +49,18 @@ export const MonitorDetailsLink = ({ name, configId, locationId, updateUrl }: Pr const href = history.createHref({ pathname: locId ? `monitor/${configId}?locationId=${locId}` : `monitor/${configId}`, }); + return ; +}; + +const MonitorDetailsLink = ({ name, configId }: Props) => { + const history = useHistory(); + const href = history.createHref({ + pathname: `monitor/${configId}`, + }); + return ; +}; + +const MonitorLink = ({ href, name }: { href: string; name: string }) => { return ( {name} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx index abcb69c5da9ba..6c4c7d5103c19 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.test.tsx @@ -115,6 +115,9 @@ describe('MonitorEditPage', () => { locationsLoaded: true, loading: false, }, + monitorDetails: { + syntheticsMonitorLoading: true, + }, }, }); @@ -173,6 +176,10 @@ describe('MonitorEditPage', () => { locationsLoaded: true, loading: false, }, + monitorDetails: { + syntheticsMonitorLoading: false, + syntheticsMonitorError: new Error('test error'), + }, }, }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx index f339c69610672..9bfd306c4a6d9 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx @@ -10,22 +10,27 @@ import { useParams } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useTrackPageview, useFetcher } from '@kbn/observability-shared-plugin/public'; -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { useTrackPageview } from '@kbn/observability-shared-plugin/public'; import { CanUsePublicLocationsCallout } from './steps/can_use_public_locations_callout'; import { DisabledCallout } from '../monitors_page/management/disabled_callout'; import { useCanUsePublicLocations } from '../../../../hooks/use_capabilities'; import { EditMonitorNotFound } from './edit_monitor_not_found'; import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout'; import { ConfigKey, SourceType } from '../../../../../common/runtime_types'; -import { getServiceLocations, selectServiceLocationsState } from '../../state'; +import { + getMonitorAction, + getServiceLocations, + selectServiceLocationsState, + selectSyntheticsMonitor, + selectSyntheticsMonitorError, + selectSyntheticsMonitorLoading, +} from '../../state'; import { AlertingCallout } from '../common/alerting_callout/alerting_callout'; import { MonitorSteps } from './steps'; import { MonitorForm } from './form'; import { LocationsLoadingError } from './locations_loading_error'; import { MonitorDetailsLinkPortal } from './monitor_details_portal'; import { useMonitorAddEditBreadcrumbs } from './use_breadcrumbs'; -import { getDecryptedMonitorAPI } from '../../state/monitor_management/api'; import { EDIT_MONITOR_STEPS } from './steps/step_config'; import { useMonitorNotFound } from './hooks/use_monitor_not_found'; @@ -43,17 +48,15 @@ export const MonitorEditPage: React.FC = () => { } }, [locationsLoaded, dispatch]); - const { data, loading, error } = useFetcher(() => { - return getDecryptedMonitorAPI({ id: monitorId }); - // FIXME: Dario thinks there is a better way to do this but - // he's getting tired and maybe the Synthetics folks can fix it - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const data = useSelector(selectSyntheticsMonitor); + const isLoading = useSelector(selectSyntheticsMonitorLoading); + const error = useSelector(selectSyntheticsMonitorError); - const monitorNotFoundError = useMonitorNotFound( - error as IHttpFetchError, - data?.id - ); + useEffect(() => { + dispatch(getMonitorAction.get({ monitorId })); + }, [dispatch, monitorId]); + + const monitorNotFoundError = useMonitorNotFound(error, data?.id); const canUsePublicLocations = useCanUsePublicLocations(data?.[ConfigKey.LOCATIONS]); @@ -93,7 +96,7 @@ export const MonitorEditPage: React.FC = () => { ); } - return data && locationsLoaded && !loading && !error ? ( + return data && locationsLoaded && !isLoading && !error ? ( <> diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx index 1f2eadb7c09fc..1f4fab7e0d977 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx @@ -7,7 +7,7 @@ import { useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; -import { ConfigKey, EncryptedSyntheticsSavedMonitor } from '../../../../../../common/runtime_types'; +import { ConfigKey } from '../../../../../../common/runtime_types'; import { useSyntheticsRefreshContext } from '../../../contexts'; import { getMonitorAction, @@ -40,7 +40,8 @@ export const useSelectedMonitor = (monId?: string) => { monitorId && monitorFromList && monitorFromList[ConfigKey.CONFIG_ID] === monitorId; const isLoadedSyntheticsMonitorValid = monitorId && syntheticsMonitor && syntheticsMonitor[ConfigKey.CONFIG_ID] === monitorId; - const availableMonitor: EncryptedSyntheticsSavedMonitor | null = isLoadedSyntheticsMonitorValid + + const availableMonitor = isLoadedSyntheticsMonitorValid ? syntheticsMonitor : isMonitorFromListValid ? monitorFromList diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx index dad59de6d630b..a575ecc110eb4 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx @@ -23,11 +23,6 @@ TagsListMock.mockReturnValue(
Tags list
); describe('Monitor Detail Flyout', () => { beforeEach(() => { - jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({ - status: observabilitySharedPublic.FETCH_STATUS.PENDING, - data: null, - refetch: () => null, - }); jest .spyOn(observabilitySharedPublic, 'useTheme') .mockReturnValue({ eui: { euiColorVis0: 'red', euiColorVis9: 'red' } } as any); @@ -76,11 +71,6 @@ describe('Monitor Detail Flyout', () => { it('renders error boundary for fetch failure', () => { const testErrorText = 'This is a test error'; - jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({ - status: observabilitySharedPublic.FETCH_STATUS.FAILURE, - error: new Error('This is a test error'), - refetch: () => null, - }); const { getByText } = render( { onClose={jest.fn()} onEnabledChange={jest.fn()} onLocationChange={jest.fn()} - /> + />, + { + state: { + monitorDetails: { + syntheticsMonitorError: { body: { message: 'This is a test error' } }, + }, + }, + } ); getByText(testErrorText); }); it('renders loading state while fetching', () => { - jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({ - status: observabilitySharedPublic.FETCH_STATUS.LOADING, - refetch: jest.fn(), - }); - const { getByRole } = render( { onClose={jest.fn()} onEnabledChange={jest.fn()} onLocationChange={jest.fn()} - /> + />, + { + state: { + monitorDetails: { + syntheticsMonitorLoading: true, + }, + }, + } ); expect(getByRole('progressbar')); }); it('renders details for fetch success', () => { - jest.spyOn(observabilitySharedPublic, 'useFetcher').mockReturnValue({ - status: observabilitySharedPublic.FETCH_STATUS.SUCCESS, - data: { - enabled: true, - type: 'http', - name: 'test-monitor', - schedule: { - number: '1', - unit: 'm', - }, - tags: ['prod'], - config_id: 'test-id', - }, - refetch: jest.fn(), - }); const detailLink = '/app/synthetics/monitor/test-id'; jest.spyOn(monitorDetailLocator, 'useMonitorDetailLocator').mockReturnValue(detailLink); jest.spyOn(monitorDetailLocator, 'useMonitorDetailLocator').mockReturnValue(detailLink); @@ -146,7 +130,24 @@ describe('Monitor Detail Flyout', () => { onClose={jest.fn()} onEnabledChange={jest.fn()} onLocationChange={jest.fn()} - /> + />, + { + state: { + monitorDetails: { + syntheticsMonitor: { + enabled: true, + type: 'http', + name: 'test-monitor', + schedule: { + number: '1', + unit: 'm', + }, + tags: ['prod'], + config_id: 'test-id', + } as any, + }, + }, + } ); expect(getByText('Every 1 minute')); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx index 8c07b3e0cad2d..22dff2d8ddef2 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx @@ -29,7 +29,7 @@ import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useTheme, FETCH_STATUS, useFetcher } from '@kbn/observability-shared-plugin/public'; +import { useTheme } from '@kbn/observability-shared-plugin/public'; import { useOverviewStatus } from '../../hooks/use_overview_status'; import { MonitorDetailsPanel } from '../../../common/components/monitor_details_panel'; import { ClientPluginsStart } from '../../../../../../plugin'; @@ -37,8 +37,12 @@ import { LocationsStatus, useStatusByLocation } from '../../../../hooks/use_stat import { MonitorEnabled } from '../../management/monitor_list_table/monitor_enabled'; import { ActionsPopover } from './actions_popover'; import { + getMonitorAction, selectMonitorUpsertStatus, selectServiceLocationsState, + selectSyntheticsMonitor, + selectSyntheticsMonitorError, + selectSyntheticsMonitorLoading, setFlyoutConfig, } from '../../../../state'; import { useMonitorDetail } from '../../../../hooks/use_monitor_detail'; @@ -46,7 +50,6 @@ import { ConfigKey, EncryptedSyntheticsMonitor, OverviewStatusMetaData } from '. import { useMonitorDetailLocator } from '../../../../hooks/use_monitor_detail_locator'; import { MonitorStatus } from '../../../common/components/monitor_status'; import { MonitorLocationSelect } from '../../../common/components/monitor_location_select'; -import { fetchSyntheticsMonitor } from '../../../../state/monitor_details/api'; interface Props { configId: string; @@ -250,21 +253,15 @@ export function MonitorDetailFlyout(props: Props) { }, [dispatch]); const upsertStatus = useSelector(selectMonitorUpsertStatus(configId)); + const monitorObject = useSelector(selectSyntheticsMonitor); + const isLoading = useSelector(selectSyntheticsMonitorLoading); + const error = useSelector(selectSyntheticsMonitorError); const upsertSuccess = upsertStatus?.status === 'success'; - const { - data: monitorObject, - error, - status, - loading, - } = useFetcher( - () => fetchSyntheticsMonitor({ monitorId: configId }), - // FIXME: Dario thinks there is a better way to do this but - // he's getting tired and maybe the Synthetics folks can fix it - // eslint-disable-next-line react-hooks/exhaustive-deps - [configId, upsertSuccess] - ); + useEffect(() => { + dispatch(getMonitorAction.get({ monitorId: configId })); + }, [configId, dispatch, upsertSuccess]); const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); @@ -283,9 +280,9 @@ export function MonitorDetailFlyout(props: Props) { onClose={props.onClose} paddingSize="none" > - {status === FETCH_STATUS.FAILURE && {error?.message}} - {status === FETCH_STATUS.LOADING && } - {status === FETCH_STATUS.SUCCESS && monitorObject && ( + {error && !isLoading && {error?.body?.message}} + {isLoading && } + {monitorObject && ( <> @@ -332,7 +329,7 @@ export function MonitorDetailFlyout(props: Props) { ...monitorObject, id, }} - loading={Boolean(loading)} + loading={Boolean(isLoading)} /> diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts index 26e0a4c7f4c41..dd0db45112113 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/actions.ts @@ -7,21 +7,16 @@ import { createAction } from '@reduxjs/toolkit'; import { MostRecentPingsRequest } from './api'; -import { - Ping, - PingsResponse, - EncryptedSyntheticsSavedMonitor, -} from '../../../../../common/runtime_types'; +import { Ping, PingsResponse, SyntheticsMonitorWithId } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; export const setMonitorDetailsLocationAction = createAction( '[MONITOR SUMMARY] SET LOCATION' ); -export const getMonitorAction = createAsyncAction< - { monitorId: string }, - EncryptedSyntheticsSavedMonitor ->('[MONITOR DETAILS] GET MONITOR'); +export const getMonitorAction = createAsyncAction<{ monitorId: string }, SyntheticsMonitorWithId>( + '[MONITOR DETAILS] GET MONITOR' +); export const getMonitorLastRunAction = createAsyncAction< { monitorId: string; locationId: string }, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts index 8a75ab6884560..6fa59e4c175d1 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/api.ts @@ -8,10 +8,10 @@ import moment from 'moment'; import { apiService } from '../../../../utils/api_service'; import { - EncryptedSyntheticsSavedMonitor, EncryptedSyntheticsMonitorCodec, PingsResponse, PingsResponseType, + SyntheticsMonitorWithId, } from '../../../../../common/runtime_types'; import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants'; @@ -67,11 +67,13 @@ export const fetchSyntheticsMonitor = async ({ monitorId, }: { monitorId: string; -}): Promise => - apiService.get( +}): Promise => { + return apiService.get( SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId), { + internal: true, version: INITIAL_REST_VERSION, }, EncryptedSyntheticsMonitorCodec ); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/index.ts index 162ed90dfb928..d247acba5d626 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/index.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/index.ts @@ -6,7 +6,7 @@ */ import { createReducer } from '@reduxjs/toolkit'; -import { EncryptedSyntheticsSavedMonitor, Ping } from '../../../../../common/runtime_types'; +import { Ping, SyntheticsMonitorWithId } from '../../../../../common/runtime_types'; import { checkIsStalePing } from '../../utils/monitor_test_result/check_pings'; import { enableMonitorAlertAction } from '../monitor_list/actions'; @@ -34,7 +34,8 @@ export interface MonitorDetailsState { loaded: boolean; }; syntheticsMonitorLoading: boolean; - syntheticsMonitor: EncryptedSyntheticsSavedMonitor | null; + syntheticsMonitor: SyntheticsMonitorWithId | null; + syntheticsMonitorError?: IHttpSerializedFetchError | null; syntheticsMonitorDispatchedAt: number; error: IHttpSerializedFetchError | null; selectedLocationId: string | null; @@ -97,15 +98,15 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => { .addCase(getMonitorAction.get, (state, action) => { state.syntheticsMonitorDispatchedAt = action.meta.dispatchedAt; state.syntheticsMonitorLoading = true; - state.error = null; + state.syntheticsMonitorError = null; }) .addCase(getMonitorAction.success, (state, action) => { state.syntheticsMonitor = action.payload; state.syntheticsMonitorLoading = false; - state.error = null; + state.syntheticsMonitorError = null; }) .addCase(getMonitorAction.fail, (state, action) => { - state.error = action.payload; + state.syntheticsMonitorError = action.payload; state.syntheticsMonitorLoading = false; }) .addCase(enableMonitorAlertAction.success, (state, action) => { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts index af30569836e44..47d116594eb3b 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_details/selectors.ts @@ -27,3 +27,14 @@ export const selectMonitorPingsMetadata = createSelector(getState, (state) => st export const selectPingsError = createSelector(getState, (state) => state.error); export const selectStatusFilter = createSelector(getState, (state) => state.statusFilter); + +export const selectSyntheticsMonitor = createSelector(getState, (state) => state.syntheticsMonitor); +export const selectSyntheticsMonitorError = createSelector( + getState, + (state) => state.syntheticsMonitorError +); + +export const selectSyntheticsMonitorLoading = createSelector( + getState, + (state) => state.syntheticsMonitorLoading +); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts index d15ea77d0d1a4..e914b27a26b67 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/actions.ts @@ -11,6 +11,7 @@ import { MonitorManagementListResult, MonitorFiltersResult, EncryptedSyntheticsSavedMonitor, + SyntheticsMonitorWithId, } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; @@ -34,7 +35,7 @@ export const fetchUpsertFailureAction = createAction( export const enableMonitorAlertAction = createAsyncAction< UpsertMonitorRequest, - EncryptedSyntheticsSavedMonitor, + SyntheticsMonitorWithId, UpsertMonitorError >('enableMonitorAlertAction'); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts index 4994921598997..bab1062a683a4 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/api.ts @@ -70,7 +70,7 @@ export const fetchUpsertMonitor = async ({ null, { version: INITIAL_REST_VERSION, - ui: true, + internal: true, } ); } else { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/effects.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/effects.ts index ac142d7c52055..bbec528db6de2 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/effects.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_list/effects.ts @@ -9,7 +9,11 @@ import { PayloadAction } from '@reduxjs/toolkit'; import { call, put, takeEvery, select, takeLatest, debounce } from 'redux-saga/effects'; import { quietFetchOverviewStatusAction } from '../overview_status'; import { enableDefaultAlertingAction } from '../alert_rules'; -import { ConfigKey, EncryptedSyntheticsSavedMonitor } from '../../../../../common/runtime_types'; +import { + ConfigKey, + EncryptedSyntheticsSavedMonitor, + SyntheticsMonitorWithId, +} from '../../../../../common/runtime_types'; import { kibanaService } from '../../../../utils/kibana_service'; import { MonitorOverviewPageState } from '../overview'; import { selectOverviewState } from '../overview/selectors'; @@ -48,7 +52,7 @@ export function* enableMonitorAlertEffect() { function* (action: PayloadAction): Generator { try { const response = yield call(fetchUpsertMonitor, action.payload); - yield put(enableMonitorAlertAction.success(response as EncryptedSyntheticsSavedMonitor)); + yield put(enableMonitorAlertAction.success(response as SyntheticsMonitorWithId)); sendSuccessToast(action.payload.success); if ( (response as EncryptedSyntheticsSavedMonitor)[ConfigKey.ALERT_CONFIG]?.status?.enabled diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts index 69101f77e8291..f4118bed3b14c 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/monitor_management/api.ts @@ -11,12 +11,12 @@ import { apiService } from '../../../../utils/api_service'; import { EncryptedSyntheticsMonitor, SyntheticsMonitor, - SyntheticsMonitorCodec, ServiceLocationErrorsResponse, + SyntheticsMonitorWithId, } from '../../../../../common/runtime_types'; import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants'; -export type UpsertMonitorResponse = ServiceLocationErrorsResponse | EncryptedSyntheticsMonitor; +export type UpsertMonitorResponse = ServiceLocationErrorsResponse | SyntheticsMonitorWithId; export const createMonitorAPI = async ({ monitor, @@ -25,6 +25,7 @@ export const createMonitorAPI = async ({ }): Promise => { return await apiService.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS, monitor, null, { version: INITIAL_REST_VERSION, + internal: true, }); }; @@ -53,21 +54,11 @@ export const updateMonitorAPI = async ({ id: string; }): Promise => { return await apiService.put(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor, null, { - ui: true, + internal: true, version: INITIAL_REST_VERSION, }); }; -export const getDecryptedMonitorAPI = async ({ id }: { id: string }): Promise => - apiService.get( - SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', id), - { - decrypted: true, - version: INITIAL_REST_VERSION, - }, - SyntheticsMonitorCodec - ); - export const fetchProjectAPIKey = async ( accessToElasticManagedLocations: boolean ): Promise => { diff --git a/x-pack/plugins/observability_solution/synthetics/server/queries/get_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/queries/get_monitor.ts index 64e425951f5ff..2ccc8fe84d89c 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/queries/get_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/queries/get_monitor.ts @@ -5,12 +5,11 @@ * 2.0. */ -import { SavedObjectsClientContract } from '@kbn/core/server'; +import { SavedObject } from '@kbn/core/server'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { syntheticsMonitorType } from '../../common/types/saved_objects'; import { SyntheticsMonitorWithSecretsAttributes, - EncryptedSyntheticsMonitorAttributes, SyntheticsMonitor, } from '../../common/runtime_types'; import { normalizeSecrets } from '../synthetics_service/utils/secrets'; @@ -18,28 +17,22 @@ import { normalizeSecrets } from '../synthetics_service/utils/secrets'; export const getSyntheticsMonitor = async ({ monitorId, encryptedSavedObjectsClient, - savedObjectsClient, + spaceId, }: { monitorId: string; + spaceId: string; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - savedObjectsClient: SavedObjectsClientContract; -}): Promise => { +}): Promise> => { try { - const encryptedMonitor = await savedObjectsClient.get( - syntheticsMonitorType, - monitorId - ); - const decryptedMonitor = await encryptedSavedObjectsClient.getDecryptedAsInternalUser( syntheticsMonitorType, monitorId, { - namespace: encryptedMonitor.namespaces?.[0], + namespace: spaceId, } ); - const { attributes } = normalizeSecrets(decryptedMonitor); - return attributes; + return normalizeSecrets(decryptedMonitor); } catch (e) { throw e; } diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor.ts index 57f305b0ec2ef..f2c8f0974a6b4 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor.ts @@ -13,7 +13,7 @@ import { AddEditMonitorAPI, CreateMonitorPayLoad } from './add_monitor/add_monit import { SyntheticsRestApiRouteFactory } from '../types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; import { normalizeAPIConfig, validateMonitor } from './monitor_validation'; -import { mapSavedObjectToMonitor } from './helper'; +import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor'; export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'POST', @@ -26,13 +26,18 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ id: schema.maybe(schema.string()), preserve_namespace: schema.maybe(schema.boolean()), gettingStarted: schema.maybe(schema.boolean()), + internal: schema.maybe( + schema.boolean({ + defaultValue: false, + }) + ), }), }, }, handler: async (routeContext): Promise => { const { request, response, server } = routeContext; // usually id is auto generated, but this is useful for testing - const { id } = request.query; + const { id, internal } = request.query; const addMonitorAPI = new AddEditMonitorAPI(routeContext); @@ -113,7 +118,7 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ addMonitorAPI.initDefaultAlerts(newMonitor.attributes.name); addMonitorAPI.setupGettingStarted(newMonitor.id); - return mapSavedObjectToMonitor(newMonitor); + return mapSavedObjectToMonitor({ monitor: newMonitor, internal }); } catch (getErr) { server.logger.error(getErr); if (getErr instanceof InvalidLocationError) { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts index 359f3373cfd35..b80a4f5be6825 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts @@ -188,7 +188,7 @@ export class AddEditMonitorAPI { prevLocations?: MonitorFields['locations'] ) { const { savedObjectsClient, syntheticsMonitorClient, request } = this.routeContext; - const ui = Boolean((request.query as { ui?: boolean })?.ui); + const internal = Boolean((request.query as { internal?: boolean })?.internal); const { locations, private_locations: privateLocations, @@ -212,7 +212,7 @@ export class AddEditMonitorAPI { if (!locations && !privateLocations && prevLocations) { locationsVal = prevLocations; } else { - const monitorLocations = parseMonitorLocations(monitorPayload, prevLocations, ui); + const monitorLocations = parseMonitorLocations(monitorPayload, prevLocations, internal); if (monitorLocations.privateLocations.length > 0) { this.allPrivateLocations = await getPrivateLocations(savedObjectsClient); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/utils.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/utils.ts index cd3c05855629d..0d096ee21745f 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/utils.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/utils.ts @@ -25,7 +25,7 @@ export const getPrivateLocationsForMonitor = async ( export const parseMonitorLocations = ( monitorPayload: CreateMonitorPayLoad, prevLocations?: MonitorFields['locations'], - ui: boolean = false + internal: boolean = false ) => { const { locations, private_locations: privateLocations } = monitorPayload; @@ -37,11 +37,11 @@ export const parseMonitorLocations = ( let pvtLocs = [...(privateLocations ?? []), ...extractPvtLocs]?.map((loc) => typeof loc === 'string' ? loc : loc.id ); - if (ui && !privateLocations && !locations && prevLocations) { + if (internal && !privateLocations && !locations && prevLocations) { locs = prevLocations.filter((loc) => loc.isServiceManaged).map((loc) => loc.id); pvtLocs = prevLocations.filter((loc) => !loc.isServiceManaged).map((loc) => loc.id); } else { - if (prevLocations && !ui) { + if (prevLocations && !internal) { if (!locations && !privateLocations) { locs = prevLocations.filter((loc) => loc.isServiceManaged).map((loc) => loc.id); pvtLocs = prevLocations.filter((loc) => !loc.isServiceManaged).map((loc) => loc.id); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts index b2871f6a9accb..d9261847db1ab 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -14,7 +14,7 @@ import { AddEditMonitorAPI, CreateMonitorPayLoad } from './add_monitor/add_monit import { ELASTIC_MANAGED_LOCATIONS_DISABLED } from './add_monitor_project'; import { getDecryptedMonitor } from '../../saved_objects/synthetics_monitor'; import { getPrivateLocations } from '../../synthetics_service/get_private_locations'; -import { mergeSourceMonitor } from './helper'; +import { mergeSourceMonitor } from './formatters/saved_object_to_monitor'; import { RouteContext, SyntheticsRestApiRouteFactory } from '../types'; import { syntheticsMonitorType } from '../../../common/types/saved_objects'; import { @@ -33,7 +33,7 @@ import { formatTelemetryUpdateEvent, } from '../telemetry/monitor_upgrade_sender'; import { formatSecrets, normalizeSecrets } from '../../synthetics_service/utils/secrets'; -import { mapSavedObjectToMonitor } from './helper'; +import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor'; // Simplify return promise type and type it with runtime_types export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ @@ -46,7 +46,11 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( monitorId: schema.string(), }), query: schema.object({ - ui: schema.maybe(schema.boolean()), + internal: schema.maybe( + schema.boolean({ + defaultValue: false, + }) + ), }), body: schema.any(), }, @@ -55,7 +59,7 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( const { request, response, spaceId, server } = routeContext; const { logger } = server; const monitor = request.body as SyntheticsMonitor; - const reqQuery = request.query as { ui?: boolean }; + const reqQuery = request.query as { internal?: boolean }; const { monitorId } = request.params; if (!monitor || typeof monitor !== 'object' || isEmpty(monitor) || Array.isArray(monitor)) { @@ -86,7 +90,7 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( const previousMonitor = await getDecryptedMonitor(server, monitorId, spaceId); const normalizedPreviousMonitor = normalizeSecrets(previousMonitor).attributes; - if (normalizedPreviousMonitor.origin !== 'ui' && !reqQuery.ui) { + if (normalizedPreviousMonitor.origin !== 'ui' && !reqQuery.internal) { return response.badRequest(getInvalidOriginError(monitor)); } @@ -170,9 +174,13 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( }); } - return mapSavedObjectToMonitor( - editedMonitorSavedObject as SavedObject - ); + return mapSavedObjectToMonitor({ + internal: reqQuery.internal, + monitor: { + ...(editedMonitorSavedObject as SavedObject), + created_at: previousMonitor.created_at, + }, + }); } catch (updateErr) { if (SavedObjectsErrorHelpers.isNotFoundError(updateErr)) { return getMonitorNotFoundResponse(response, monitorId); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/helper.test.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/formatters/saved_object_to_monitor.test.ts similarity index 64% rename from x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/helper.test.ts rename to x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/formatters/saved_object_to_monitor.test.ts index 9e0bbf10f45e0..0ddfda0f77874 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/helper.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/formatters/saved_object_to_monitor.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { mapSavedObjectToMonitor, mergeSourceMonitor } from './helper'; -import { EncryptedSyntheticsMonitor } from '../../../common/runtime_types'; +import { mapSavedObjectToMonitor, mergeSourceMonitor } from './saved_object_to_monitor'; +import { EncryptedSyntheticsMonitor } from '../../../../common/runtime_types'; describe('mergeSourceMonitor', () => { it('should merge keys', function () { @@ -66,8 +66,82 @@ describe('mergeSourceMonitor', () => { ]); }); - it('should omit null or undefined values', () => { - const result = mapSavedObjectToMonitor({ attributes: testMonitor } as any); + it('should not omit null or undefined values', () => { + const result = mapSavedObjectToMonitor({ monitor: { attributes: testMonitor } } as any); + + expect(result).toEqual({ + alert: { + status: { + enabled: true, + }, + tls: { + enabled: true, + }, + }, + config_id: 'ae88f0aa-9c7d-4a5f-96dc-89d65a0ca947', + custom_heartbeat_id: 'todos-lightweight-test-projects-default', + enabled: true, + id: 'todos-lightweight-test-projects-default', + ipv4: true, + ipv6: true, + locations: [ + { + geo: { + lat: 41.25, + lon: -95.86, + }, + id: 'us_central', + isServiceManaged: true, + label: 'North America - US Central', + }, + ], + max_redirects: 0, + mode: 'any', + name: 'Todos Lightweight', + namespace: 'default', + original_space: 'default', + proxy_url: '', + retest_on_failure: true, + revision: 21, + schedule: { + number: '3', + unit: 'm', + }, + 'service.name': '', + tags: [], + timeout: '16', + type: 'http', + url: '${devUrl}', + 'url.port': null, + ssl: { + certificate: '', + certificate_authorities: '', + supported_protocols: ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + verification_mode: 'full', + key: 'test-key', + }, + response: { + include_body: 'on_error', + include_body_max_bytes: '1024', + include_headers: true, + }, + check: { + request: { + method: 'GET', + }, + response: { + status: ['404'], + }, + }, + params: {}, + }); + }); + + it('should not omit null or undefined values with ui', () => { + const result = mapSavedObjectToMonitor({ + monitor: { attributes: { ...testMonitor } }, + internal: true, + } as any); expect(result).toEqual({ __ui: { @@ -100,27 +174,37 @@ describe('mergeSourceMonitor', () => { label: 'North America - US Central', }, ], - max_attempts: 2, max_redirects: '0', mode: 'any', name: 'Todos Lightweight', namespace: 'default', - origin: 'project', original_space: 'default', project_id: 'test-projects', - 'response.include_body': 'on_error', - 'response.include_body_max_bytes': '1024', - 'response.include_headers': true, + proxy_url: '', revision: 21, schedule: { number: '3', unit: 'm', }, + 'service.name': '', + 'ssl.certificate': '', + 'ssl.certificate_authorities': '', + 'ssl.key': 'test-key', 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], 'ssl.verification_mode': 'full', + 'response.include_body': 'on_error', + 'response.include_body_max_bytes': '1024', + 'response.include_headers': true, + tags: [], timeout: '16', type: 'http', - url: '${devUrl}', + 'url.port': null, + form_monitor_type: 'http', + hash: 'f4b6u3Q/PMK5KzEtPeMNzXJBA46rt+yilohaAoqMzqk=', + journey_id: 'todos-lightweight', + max_attempts: 2, + origin: 'project', + urls: '${devUrl}', }); }); }); @@ -179,6 +263,7 @@ const testMonitor = { ipv4: true, ipv6: true, 'ssl.certificate_authorities': '', + 'ssl.key': 'test-key', 'ssl.certificate': '', 'ssl.verification_mode': 'full', 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/formatters/saved_object_to_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/formatters/saved_object_to_monitor.ts new file mode 100644 index 0000000000000..4f5a3b194c896 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/formatters/saved_object_to_monitor.ts @@ -0,0 +1,158 @@ +/* + * 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 { SavedObject } from '@kbn/core/server'; +import { mergeWith, omit, omitBy } from 'lodash'; +import { + ConfigKey, + EncryptedSyntheticsMonitor, + MonitorFields, + MonitorFieldsResult, +} from '../../../../common/runtime_types'; + +const keysToOmit = [ + ConfigKey.URLS, + ConfigKey.SOURCE_INLINE, + ConfigKey.HOSTS, + ConfigKey.CONFIG_HASH, + ConfigKey.JOURNEY_ID, + ConfigKey.FORM_MONITOR_TYPE, + ConfigKey.MAX_ATTEMPTS, + ConfigKey.MONITOR_SOURCE_TYPE, + ConfigKey.METADATA, + ConfigKey.SOURCE_PROJECT_CONTENT, + ConfigKey.PROJECT_ID, + ConfigKey.JOURNEY_FILTERS_MATCH, + ConfigKey.JOURNEY_FILTERS_TAGS, + ConfigKey.MONITOR_SOURCE_TYPE, +]; + +type Result = MonitorFieldsResult & { + url?: string; + host?: string; + inline_script?: string; + ssl: Record; + response: Record; + check: Record; +}; + +export const transformPublicKeys = (result: Result) => { + let formattedResult = { + ...result, + [ConfigKey.PARAMS]: formatParams(result), + retest_on_failure: (result[ConfigKey.MAX_ATTEMPTS] ?? 1) > 1, + ...(result[ConfigKey.HOSTS] && { host: result[ConfigKey.HOSTS] }), + ...(result[ConfigKey.URLS] && { url: result[ConfigKey.URLS] }), + }; + if (formattedResult[ConfigKey.MONITOR_TYPE] === 'browser') { + formattedResult = { + ...formattedResult, + ...(result[ConfigKey.SOURCE_INLINE] && { inline_script: result[ConfigKey.SOURCE_INLINE] }), + [ConfigKey.PLAYWRIGHT_OPTIONS]: formatPWOptions(result), + }; + } else { + formattedResult.ssl = formatNestedFields(formattedResult, 'ssl'); + formattedResult.response = formatNestedFields(formattedResult, 'response'); + formattedResult.check = formatNestedFields(formattedResult, 'check'); + if (formattedResult[ConfigKey.MAX_REDIRECTS]) { + formattedResult[ConfigKey.MAX_REDIRECTS] = Number(formattedResult[ConfigKey.MAX_REDIRECTS]); + } + } + const res = omit(formattedResult, keysToOmit) as Result; + + return omitBy( + res, + (value, key) => + key.startsWith('response.') || key.startsWith('ssl.') || key.startsWith('check.') + ); +}; + +export function mapSavedObjectToMonitor({ + monitor, + internal = false, +}: { + monitor: SavedObject; + internal?: boolean; +}) { + const result = { + ...monitor.attributes, + created_at: monitor.created_at, + updated_at: monitor.updated_at, + } as Result; + if (internal) { + return result; + } + return transformPublicKeys(result); +} +export function mergeSourceMonitor( + normalizedPreviousMonitor: EncryptedSyntheticsMonitor, + monitor: EncryptedSyntheticsMonitor +) { + return mergeWith({ ...normalizedPreviousMonitor }, monitor, customizer); +} + +// Ensure that METADATA is merged deeply, to protect AAD and prevent decryption errors +const customizer = (destVal: any, srcValue: any, key: string) => { + if (key === ConfigKey.ALERT_CONFIG) { + return { ...destVal, ...srcValue }; + } + if (key !== ConfigKey.METADATA) { + return srcValue; + } +}; + +const formatParams = (config: MonitorFields) => { + if (config[ConfigKey.PARAMS]) { + try { + return (config[ConfigKey.PARAMS] = JSON.parse(config[ConfigKey.PARAMS] ?? '{}')); + } catch (e) { + // ignore + return {}; + } + } + return {}; +}; + +const formatPWOptions = (config: MonitorFields) => { + if (config[ConfigKey.PLAYWRIGHT_OPTIONS]) { + try { + return (config[ConfigKey.PLAYWRIGHT_OPTIONS] = JSON.parse( + config[ConfigKey.PLAYWRIGHT_OPTIONS] ?? '{}' + )); + } catch (e) { + // ignore + return {}; + } + } + return {}; +}; + +// combine same nested fields into same object +const formatNestedFields = ( + config: MonitorFields | Record, + nestedKey: 'ssl' | 'response' | 'check' | 'request' +): Record => { + const nestedFields = Object.keys(config).filter((key) => + key.startsWith(`${nestedKey}.`) + ) as ConfigKey[]; + const obj: Record = {}; + + nestedFields.forEach((key) => { + const newKey = key.replace(`${nestedKey}.`, ''); + obj[newKey] = config[key]; + delete config[key]; + }); + + if (nestedKey === 'check') { + return { + request: formatNestedFields(obj, 'request'), + response: formatNestedFields(obj, 'response'), + }; + } + + return obj; +}; diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts index 69e7491a86b2b..1a07ddc832561 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor.ts @@ -12,7 +12,7 @@ import { isStatusEnabled } from '../../../common/runtime_types/monitor_managemen import { ConfigKey, EncryptedSyntheticsMonitorAttributes } from '../../../common/runtime_types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; import { getMonitorNotFoundResponse } from '../synthetics_service/service_errors'; -import { mapSavedObjectToMonitor } from './helper'; +import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor'; import { getSyntheticsMonitor } from '../../queries/get_monitor'; export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ @@ -25,7 +25,11 @@ export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ monitorId: schema.string({ minLength: 1, maxLength: 1024 }), }), query: schema.object({ - decrypted: schema.maybe(schema.boolean()), + internal: schema.maybe( + schema.boolean({ + defaultValue: false, + }) + ), }), }, }, @@ -34,36 +38,36 @@ export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ response, server: { encryptedSavedObjects, coreStart }, savedObjectsClient, + spaceId, }): Promise => { const { monitorId } = request.params; try { - const { decrypted } = request.query; + const { internal } = request.query; - if (!decrypted) { - return mapSavedObjectToMonitor( - await savedObjectsClient.get( - syntheticsMonitorType, - monitorId - ) - ); - } else { - // only user with write permissions can decrypt the monitor - const canSave = - ( - await coreStart?.capabilities.resolveCapabilities(request, { - capabilityPath: 'uptime.*', - }) - ).uptime.save ?? false; - if (!canSave) { - return response.forbidden(); - } + const canSave = + ( + await coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.save ?? false; + if (Boolean(canSave)) { + // only user with write permissions can decrypt the monitor const encryptedSavedObjectsClient = encryptedSavedObjects.getClient(); - return await getSyntheticsMonitor({ + const monitor = await getSyntheticsMonitor({ monitorId, encryptedSavedObjectsClient, - savedObjectsClient, + spaceId, + }); + return mapSavedObjectToMonitor({ monitor, internal }); + } else { + return mapSavedObjectToMonitor({ + monitor: await savedObjectsClient.get( + syntheticsMonitorType, + monitorId + ), + internal, }); } } catch (getErr) { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts index 563a7fde740bb..22584df3a7090 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitors_list.ts @@ -4,11 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { mapSavedObjectToMonitor } from './formatters/saved_object_to_monitor'; import { SyntheticsRestApiRouteFactory } from '../types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; import { getMonitors, isMonitorsQueryFiltered, QuerySchema } from '../common'; import { syntheticsMonitorType } from '../../../common/types/saved_objects'; -import { mapSavedObjectToMonitor } from './helper'; export const getAllSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'GET', @@ -39,7 +39,11 @@ export const getAllSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => return { ...rest, - monitors: savedObjects.map(mapSavedObjectToMonitor), + monitors: savedObjects.map((monitor) => + mapSavedObjectToMonitor({ + monitor, + }) + ), absoluteTotal, perPage: perPageT, syncErrors: syntheticsMonitorClient.syntheticsService.syncErrors, diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/helper.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/helper.ts deleted file mode 100644 index d5480e0013b70..0000000000000 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/helper.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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 { SavedObject } from '@kbn/core/server'; -import { mergeWith, omit, omitBy } from 'lodash'; -import { - ConfigKey, - EncryptedSyntheticsMonitor, - MonitorFields, -} from '../../../common/runtime_types'; - -const keysToOmit = [ - ConfigKey.URLS, - ConfigKey.SOURCE_INLINE, - ConfigKey.HOSTS, - ConfigKey.CONFIG_HASH, - ConfigKey.JOURNEY_ID, - ConfigKey.FORM_MONITOR_TYPE, -]; - -type Result = MonitorFields & { url?: string; host?: string; inline_script?: string }; - -export const transformPublicKeys = (result: Result) => { - if (result[ConfigKey.URLS]) { - result.url = result[ConfigKey.URLS]; - } - if (result[ConfigKey.SOURCE_INLINE]) { - result.inline_script = result[ConfigKey.SOURCE_INLINE]; - } - if (result[ConfigKey.HOSTS]) { - result.host = result[ConfigKey.HOSTS]; - } - if (result[ConfigKey.PARAMS]) { - try { - result[ConfigKey.PARAMS] = JSON.parse(result[ConfigKey.PARAMS] ?? '{}'); - } catch (e) { - // ignore - } - } - if (result[ConfigKey.PLAYWRIGHT_OPTIONS]) { - try { - result[ConfigKey.PLAYWRIGHT_OPTIONS] = JSON.parse( - result[ConfigKey.PLAYWRIGHT_OPTIONS] ?? '{}' - ); - } catch (e) { - // ignore - } - } - return omit(result, keysToOmit) as Result; -}; - -export function mapSavedObjectToMonitor( - so: SavedObject -) { - let result = Object.assign(so.attributes, { - created_at: so.created_at, - updated_at: so.updated_at, - }) as Result; - result = transformPublicKeys(result); - - // omit undefined value or null value - return omitBy(result, removeMonitorEmptyValues); -} -export function mergeSourceMonitor( - normalizedPreviousMonitor: EncryptedSyntheticsMonitor, - monitor: EncryptedSyntheticsMonitor -) { - return mergeWith({ ...normalizedPreviousMonitor }, monitor, customizer); -} - -// Ensure that METADATA is merged deeply, to protect AAD and prevent decryption errors -const customizer = (destVal: any, srcValue: any, key: string) => { - if (key === ConfigKey.ALERT_CONFIG) { - return { ...destVal, ...srcValue }; - } - if (key !== ConfigKey.METADATA) { - return srcValue; - } -}; - -export const removeMonitorEmptyValues = (v: any) => { - // value is falsy - return ( - v === undefined || - v === null || - // value is empty string - (typeof v === 'string' && v.trim() === '') || - // is empty array - (Array.isArray(v) && v.length === 0) || - // object is has no values - (typeof v === 'object' && Object.keys(v).length === 0) - ); -}; diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts index 650c9b964aaec..b4dd34952c7a8 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.test.ts @@ -163,6 +163,7 @@ describe('getNormalizeCommonFields', () => { timeout: '16', params: '', max_attempts: 2, + labels: {}, }, }); } @@ -228,6 +229,7 @@ describe('getNormalizeCommonFields', () => { timeout: '16', params: '', max_attempts: 2, + labels: {}, }, }); }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts index d657ad376b38d..0a3aa8295a94d 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/common_fields.ts @@ -94,6 +94,7 @@ export const getNormalizeCommonFields = ({ : defaultFields[ConfigKey.PARAMS], // picking out keys specifically, so users can't add arbitrary fields [ConfigKey.ALERT_CONFIG]: getAlertConfig(monitor), + [ConfigKey.LABELS]: monitor.fields || defaultFields[ConfigKey.LABELS], }; return { normalizedFields, errors }; }; @@ -459,7 +460,9 @@ export const flattenAndFormatObject = (obj: Record, prefix = '' return acc; }, {}); -export const normalizeYamlConfig = (monitor: NormalizedProjectProps['monitor']) => { +export const normalizeYamlConfig = (data: NormalizedProjectProps['monitor']) => { + // we map fields to labels + const { fields: _fields, ...monitor } = data; const defaultFields = DEFAULT_FIELDS[monitor.type as MonitorTypeEnum]; const supportedKeys = Object.keys(defaultFields); const flattenedConfig = flattenAndFormatObject(monitor, '', supportedKeys); diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts index caef8a1ba3381..3e8e8647568c4 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.ts @@ -124,5 +124,5 @@ export const formatMaxRedirects = (value?: string | number): string => { const defaultFields = DEFAULT_FIELDS[MonitorTypeEnum.HTTP]; - return value ?? defaultFields[ConfigKey.MAX_REDIRECTS]; + return value ?? String(defaultFields[ConfigKey.MAX_REDIRECTS]); }; diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts index 311e8072c4aea..89fc77c034072 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import epct from 'expect'; import moment from 'moment/moment'; import { v4 as uuidv4 } from 'uuid'; -import { omit, omitBy } from 'lodash'; +import { omit } from 'lodash'; import { ConfigKey, MonitorTypeEnum, @@ -23,10 +23,7 @@ import { format as formatUrl } from 'url'; import supertest from 'supertest'; import { getServiceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key'; import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects'; -import { - removeMonitorEmptyValues, - transformPublicKeys, -} from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper'; +import { transformPublicKeys } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/formatters/saved_object_to_monitor'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from './helper/get_fixture_json'; import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service'; @@ -54,8 +51,10 @@ export const addMonitorAPIHelper = async (supertestAPI: any, monitor: any, statu return result.body; }; +export const keyToOmitList = ['created_at', 'updated_at', 'id', 'config_id', 'form_monitor_type']; + export const omitMonitorKeys = (monitor: any) => { - return omitBy(transformPublicKeys(monitor), removeMonitorEmptyValues); + return omit(transformPublicKeys(monitor), keyToOmitList); }; export default function ({ getService }: FtrProviderContext) { @@ -154,10 +153,10 @@ export default function ({ getService }: FtrProviderContext) { const { body: apiResponse } = await addMonitorAPI(newMonitor); - epct(apiResponse).toEqual(epct.objectContaining({ max_attempts: maxAttempts })); + epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: false })); }); - it('can enable retries', async () => { + it('can enable retries with max attempts', async () => { const maxAttempts = 2; const newMonitor = { max_attempts: maxAttempts, @@ -169,7 +168,21 @@ export default function ({ getService }: FtrProviderContext) { const { body: apiResponse } = await addMonitorAPI(newMonitor); - epct(apiResponse).toEqual(epct.objectContaining({ max_attempts: maxAttempts })); + epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: true })); + }); + + it('can enable retries', async () => { + const newMonitor = { + retest_on_failure: false, + urls: 'https://elastic.co', + name: `Sample name ${uuidv4()}`, + type: 'http', + locations: [localLoc], + }; + + const { body: apiResponse } = await addMonitorAPI(newMonitor); + + epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: false })); }); it('cannot create a invalid monitor without a monitor type', async () => { diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index 3840d19fbc3a7..044e66fe239f7 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -26,7 +26,7 @@ import { INSTALLED_VERSION, PrivateLocationTestService, } from './services/private_location_test_service'; -import { addMonitorAPIHelper, omitMonitorKeys } from './add_monitor'; +import { addMonitorAPIHelper, keyToOmitList, omitMonitorKeys } from './add_monitor'; import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service'; export default function ({ getService }: FtrProviderContext) { @@ -211,12 +211,10 @@ export default function ({ getService }: FtrProviderContext) { const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body; expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); - expect(apiResponse.body).eql( + expect(omit(apiResponse.body, keyToOmitList)).eql( omitMonitorKeys({ ...omit(httpMonitorJson, ['urls']), url: httpMonitorJson.urls, - [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, - [ConfigKey.CONFIG_ID]: apiResponse.body.id, updated_at: updatedAt, revision: 2, }) @@ -269,7 +267,7 @@ export default function ({ getService }: FtrProviderContext) { ); await supertestAPI - .put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId + '?ui=true') + .put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId + '?internal=true') .set('kbn-xsrf', 'true') .send(httpMonitorJson) .expect(200); @@ -370,15 +368,11 @@ export default function ({ getService }: FtrProviderContext) { const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body; expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); - expect(apiResponse.body).eql( + expect(omit(apiResponse.body, keyToOmitList)).eql( omitMonitorKeys({ ...monitor, - [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, - [ConfigKey.CONFIG_ID]: apiResponse.body.id, [ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID), url: apiResponse.body.url, - created_at: createdAt, - updated_at: updatedAt, }) ); monitorId = apiResponse.body.id; diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index 074f318e77fcf..bd6f94a44f6c1 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -170,17 +170,20 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); const decryptedCreatedMonitor = await monitorTestService.getMonitor( - createdMonitorsResponse.body.monitors[0].config_id + createdMonitorsResponse.body.monitors[0].config_id, + { + internal: true, + } ); - expect(decryptedCreatedMonitor.body).to.eql({ + expect(decryptedCreatedMonitor.rawBody).to.eql({ __ui: { script_source: { file_name: '', is_generated_script: false, }, }, - config_id: decryptedCreatedMonitor.body.config_id, + config_id: decryptedCreatedMonitor.rawBody.config_id, custom_heartbeat_id: `${journeyId}-${project}-default`, enabled: true, alert: { @@ -241,6 +244,8 @@ export default function ({ getService }: FtrProviderContext) { id: `${journeyId}-${project}-default`, hash: 'ekrjelkjrelkjre', max_attempts: 2, + updated_at: decryptedCreatedMonitor.rawBody.updated_at, + created_at: decryptedCreatedMonitor.rawBody.created_at, labels: {}, }); } @@ -341,17 +346,20 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - const decryptedCreatedMonitor = await monitorTestService.getMonitor( - createdMonitorsResponse.body.monitors[0].config_id + const { rawBody: decryptedCreatedMonitor } = await monitorTestService.getMonitor( + createdMonitorsResponse.body.monitors[0].config_id, + { + internal: true, + } ); - expect(decryptedCreatedMonitor.body).to.eql({ + expect(decryptedCreatedMonitor).to.eql({ __ui: { is_tls_enabled: isTLSEnabled, }, 'check.request.method': 'POST', 'check.response.status': ['200'], - config_id: decryptedCreatedMonitor.body.config_id, + config_id: decryptedCreatedMonitor.config_id, custom_heartbeat_id: `${journeyId}-${project}-default`, 'check.response.body.negative': [], 'check.response.body.positive': ['${testLocal1}', 'saved'], @@ -364,8 +372,10 @@ export default function ({ getService }: FtrProviderContext) { type: 'text', value: '', }, - params: - '{"testLocal1":"testLocalParamsValue","testGlobalParam2":"testGlobalParamOverwrite"}', + params: JSON.stringify({ + testLocal1: 'testLocalParamsValue', + testGlobalParam2: 'testGlobalParamOverwrite', + }), 'check.request.headers': { 'Content-Type': 'application/x-www-form-urlencoded', }, @@ -427,6 +437,8 @@ export default function ({ getService }: FtrProviderContext) { ipv4: true, max_attempts: 2, labels: {}, + updated_at: decryptedCreatedMonitor.updated_at, + created_at: decryptedCreatedMonitor.created_at, }); } } finally { @@ -478,15 +490,18 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - const decryptedCreatedMonitor = await monitorTestService.getMonitor( - createdMonitorsResponse.body.monitors[0].config_id + const { rawBody: decryptedCreatedMonitor } = await monitorTestService.getMonitor( + createdMonitorsResponse.body.monitors[0].config_id, + { + internal: true, + } ); - expect(decryptedCreatedMonitor.body).to.eql({ + expect(decryptedCreatedMonitor).to.eql({ __ui: { is_tls_enabled: isTLSEnabled, }, - config_id: decryptedCreatedMonitor.body.config_id, + config_id: decryptedCreatedMonitor.config_id, custom_heartbeat_id: `${journeyId}-${project}-default`, 'check.receive': '', 'check.send': '', @@ -545,6 +560,8 @@ export default function ({ getService }: FtrProviderContext) { params: '', max_attempts: 2, labels: {}, + updated_at: decryptedCreatedMonitor.updated_at, + created_at: decryptedCreatedMonitor.created_at, }); } } finally { @@ -594,12 +611,15 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .expect(200); - const decryptedCreatedMonitor = await monitorTestService.getMonitor( - createdMonitorsResponse.body.monitors[0].config_id + const { rawBody: decryptedCreatedMonitor } = await monitorTestService.getMonitor( + createdMonitorsResponse.body.monitors[0].config_id, + { + internal: true, + } ); - expect(decryptedCreatedMonitor.body).to.eql({ - config_id: decryptedCreatedMonitor.body.config_id, + expect(decryptedCreatedMonitor).to.eql({ + config_id: decryptedCreatedMonitor.config_id, custom_heartbeat_id: `${journeyId}-${project}-default`, enabled: true, alert: { @@ -659,6 +679,8 @@ export default function ({ getService }: FtrProviderContext) { ipv6: true, params: '', max_attempts: 2, + updated_at: decryptedCreatedMonitor.updated_at, + created_at: decryptedCreatedMonitor.created_at, labels: {}, }); } @@ -1106,8 +1128,7 @@ export default function ({ getService }: FtrProviderContext) { const decryptedCreatedMonitor = await monitorTestService.getMonitor( getResponse.body.monitors[0].config_id, - true, - SPACE_ID + { internal: true, space: SPACE_ID } ); const { monitors } = getResponse.body; expect(monitors.length).eql(1); @@ -1144,8 +1165,7 @@ export default function ({ getService }: FtrProviderContext) { const decryptedUpdatedMonitor = await monitorTestService.getMonitor( monitorsUpdated[0].config_id, - true, - SPACE_ID + { internal: true, space: SPACE_ID } ); expect(decryptedUpdatedMonitor.body[ConfigKey.SOURCE_PROJECT_CONTENT]).eql(updatedSource); } finally { diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_public_api.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_public_api.ts index df6a0b0df9056..5da370d1c634f 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_public_api.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_public_api.ts @@ -6,13 +6,8 @@ */ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; -import { omitBy } from 'lodash'; import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults'; -import { - removeMonitorEmptyValues, - transformPublicKeys, -} from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper'; import { LOCATION_REQUIRED_ERROR } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/monitor_validation'; import { FtrProviderContext } from '../../ftr_provider_context'; import { addMonitorAPIHelper, omitMonitorKeys } from './add_monitor'; @@ -101,7 +96,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('HTTP Monitor', () => { - const defaultFields = omitBy(DEFAULT_FIELDS.http, removeMonitorEmptyValues); + const defaultFields = DEFAULT_FIELDS.http; it('return error empty http', async () => { const { message, attributes } = await addMonitorAPI( { @@ -154,8 +149,7 @@ export default function ({ getService }: FtrProviderContext) { ...monitor, locations: [localLoc], name, - max_attempts: 2, - retest_on_failure: undefined, // this key is not part of the SO and should not be defined + retest_on_failure: true, }) ); }); @@ -185,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('TCP Monitor', () => { - const defaultFields = omitBy(DEFAULT_FIELDS.tcp, removeMonitorEmptyValues); + const defaultFields = DEFAULT_FIELDS.tcp; it('base tcp monitor', async () => { const monitor = { @@ -207,7 +201,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('ICMP Monitor', () => { - const defaultFields = omitBy(DEFAULT_FIELDS.icmp, removeMonitorEmptyValues); + const defaultFields = DEFAULT_FIELDS.icmp; it('base icmp monitor', async () => { const monitor = { @@ -229,7 +223,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('Browser Monitor', () => { - const defaultFields = omitBy(DEFAULT_FIELDS.browser, removeMonitorEmptyValues); + const defaultFields = DEFAULT_FIELDS.browser; it('empty browser monitor', async () => { const monitor = { @@ -259,7 +253,7 @@ export default function ({ getService }: FtrProviderContext) { }; const { body: result } = await addMonitorAPI(monitor); - expect(transformPublicKeys(result)).eql( + expect(result).eql( omitMonitorKeys({ ...defaultFields, ...monitor, diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts index 1504384cef030..522a359c6d51b 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; import { v4 as uuidv4 } from 'uuid'; -import { omit, omitBy } from 'lodash'; +import { omit } from 'lodash'; import { ConfigKey, EncryptedSyntheticsSavedMonitor, @@ -15,7 +15,6 @@ import { } from '@kbn/synthetics-plugin/common/runtime_types'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import expect from '@kbn/expect'; -import { removeMonitorEmptyValues } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from './helper/get_fixture_json'; import { omitResponseTimestamps, omitEmptyValues } from './helper/monitor'; @@ -40,12 +39,11 @@ export default function ({ getService }: FtrProviderContext) { let testPolicyId = ''; const saveMonitor = async (monitor: MonitorFields, spaceId?: string) => { + const apiURL = spaceId + ? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}` + : SYNTHETICS_API_URLS.SYNTHETICS_MONITORS; const res = await supertest - .post( - spaceId - ? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}` - : SYNTHETICS_API_URLS.SYNTHETICS_MONITORS - ) + .post(apiURL + '?internal=true') .set('kbn-xsrf', 'true') .send(monitor); @@ -55,21 +53,21 @@ export default function ({ getService }: FtrProviderContext) { expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); - return { ...rest, urls: url } as EncryptedSyntheticsSavedMonitor; + return rest as EncryptedSyntheticsSavedMonitor; }; const editMonitor = async (modifiedMonitor: MonitorFields, monitorId: string) => { const res = await supertest - .put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId) + .put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId + '?internal=true') .set('kbn-xsrf', 'true') .send(modifiedMonitor); expect(res.status).eql(200, JSON.stringify(res.body)); - const { url, ...rest } = res.body; + const { created_at: createdAt, updated_at: updatedAt } = res.body; + expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); - const result = { ...rest, urls: url } as EncryptedSyntheticsSavedMonitor; - return omitBy(omit(result, ['created_at', 'updated_at']), removeMonitorEmptyValues); + return omit(res.body, ['created_at', 'updated_at']); }; before(async () => { @@ -101,9 +99,6 @@ export default function ({ getService }: FtrProviderContext) { const savedMonitor = await saveMonitor(newMonitor as MonitorFields); const monitorId = savedMonitor[ConfigKey.CONFIG_ID]; - const { created_at: createdAt, updated_at: updatedAt } = savedMonitor; - expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); - expect(omitResponseTimestamps(savedMonitor)).eql( omitEmptyValues({ ...newMonitor, @@ -269,6 +264,7 @@ export default function ({ getService }: FtrProviderContext) { [ConfigKey.CONFIG_ID]: monitorId, [ConfigKey.MONITOR_QUERY_ID]: monitorId, name: 'test monitor - 12', + hash: configHash, }) ); @@ -399,7 +395,10 @@ export default function ({ getService }: FtrProviderContext) { .send(toUpdate) .expect(200); - const updatedResponse = await monitorTestService.getMonitor(monitorId, true, SPACE_ID); + const updatedResponse = await monitorTestService.getMonitor(monitorId, { + space: SPACE_ID, + internal: true, + }); // ensure monitor was updated expect(updatedResponse.body.urls).eql(toUpdate.urls); @@ -416,7 +415,10 @@ export default function ({ getService }: FtrProviderContext) { .send(toUpdate2) .expect(200); - const updatedResponse2 = await monitorTestService.getMonitor(monitorId, true, SPACE_ID); + const updatedResponse2 = await monitorTestService.getMonitor(monitorId, { + space: SPACE_ID, + internal: true, + }); // ensure monitor was updated expect(updatedResponse2.body.urls).eql(toUpdate2.urls); diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts index ffb44dd1b00d9..fa7c780a2d971 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor_public_api.ts @@ -5,10 +5,9 @@ * 2.0. */ import expect from '@kbn/expect'; -import { omit, omitBy } from 'lodash'; +import { omit } from 'lodash'; import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults'; -import { removeMonitorEmptyValues } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper'; import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import moment from 'moment'; import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types'; @@ -70,7 +69,7 @@ export default function ({ getService }: FtrProviderContext) { }); let monitorId = 'test-id'; - const defaultFields = omitBy(DEFAULT_FIELDS.http, removeMonitorEmptyValues); + const defaultFields = DEFAULT_FIELDS.http; it('adds test monitor', async () => { const monitor = { type: 'http', diff --git a/x-pack/test/api_integration/apis/synthetics/fixtures/browser_monitor.json b/x-pack/test/api_integration/apis/synthetics/fixtures/browser_monitor.json index 991fded06e477..1cb2d39685bf2 100644 --- a/x-pack/test/api_integration/apis/synthetics/fixtures/browser_monitor.json +++ b/x-pack/test/api_integration/apis/synthetics/fixtures/browser_monitor.json @@ -55,5 +55,6 @@ "ssl.supported_protocols": ["TLSv1.1", "TLSv1.2", "TLSv1.3"], "ssl.verification_mode": "full", "revision": 1, - "max_attempts": 2 + "max_attempts": 2, + "labels": {} } diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts index 114f7666e7965..9f266fa42fc31 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts @@ -15,6 +15,8 @@ import { import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; import expect from '@kbn/expect'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; +import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service'; +import { omitMonitorKeys } from './add_monitor'; import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from './helper/get_fixture_json'; import { LOCAL_LOCATION } from './get_filters'; @@ -26,6 +28,7 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); const retry = getService('retry'); + const monitorTestService = new SyntheticsMonitorTestService(getService); let _monitors: MonitorFields[]; let monitors: MonitorFields[]; @@ -189,22 +192,45 @@ export default function ({ getService }: FtrProviderContext) { monitors.map((mon) => ({ ...mon, name: mon.name + '4' })).map(saveMonitor) ); - const apiResponse = await supertest - .get( - SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', id1) + - '?decrypted=true' - ) - .expect(200); + const apiResponse = await monitorTestService.getMonitor(id1); + + expect(apiResponse.body).eql( + omitMonitorKeys({ + ...monitors[0], + [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, + [ConfigKey.CONFIG_ID]: apiResponse.body.id, + revision: 1, + locations: [LOCAL_LOCATION], + name: 'Test HTTP Monitor 044', + labels: {}, + }) + ); + }); - expect(apiResponse.body).eql({ - ...monitors[0], - [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, - [ConfigKey.CONFIG_ID]: apiResponse.body.id, - revision: 1, - locations: [LOCAL_LOCATION], - name: 'Test HTTP Monitor 044', - labels: {}, - }); + it('should get by id with ui query param', async () => { + const [{ id: id1 }] = await Promise.all( + monitors.map((mon) => ({ ...mon, name: mon.name + '5' })).map(saveMonitor) + ); + + const apiResponse = await monitorTestService.getMonitor(id1, { internal: true }); + + expect(apiResponse.body).eql( + omit( + { + ...monitors[0], + form_monitor_type: 'icmp', + revision: 1, + locations: [LOCAL_LOCATION], + name: 'Test HTTP Monitor 045', + hosts: '192.33.22.111:3333', + hash: '', + journey_id: '', + max_attempts: 2, + labels: {}, + }, + ['config_id', 'id', 'form_monitor_type'] + ) + ); }); it('returns 404 if monitor id is not found', async () => { diff --git a/x-pack/test/api_integration/apis/synthetics/helper/monitor.ts b/x-pack/test/api_integration/apis/synthetics/helper/monitor.ts index 25a5d4c438575..8c10fa78d9834 100644 --- a/x-pack/test/api_integration/apis/synthetics/helper/monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/helper/monitor.ts @@ -5,21 +5,17 @@ * 2.0. */ -import { omit, omitBy } from 'lodash'; -import { removeMonitorEmptyValues } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/helper'; +import { omit } from 'lodash'; export function omitResponseTimestamps(monitor: object) { - return omitBy(omit(monitor, ['created_at', 'updated_at']), removeMonitorEmptyValues); + return omit(monitor, ['created_at', 'updated_at']); } export function omitEmptyValues(monitor: object) { - const { url, ...rest } = omit(monitor, ['created_at', 'updated_at', 'form_monitor_type']) as any; + const { url, ...rest } = omit(monitor, ['created_at', 'updated_at']) as any; - return omitBy( - { - ...rest, - ...(url ? { url } : {}), - }, - removeMonitorEmptyValues - ); + return { + ...rest, + ...(url ? { url } : {}), + }; } diff --git a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts index 7bc246804f42c..e11a5523ed7b4 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts @@ -12,6 +12,8 @@ import { MonitorInspectResponse } from '@kbn/synthetics-plugin/public/apps/synth import { v4 as uuidv4 } from 'uuid'; import expect from '@kbn/expect'; import { ProjectAPIKeyResponse } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/get_api_key'; +import moment from 'moment/moment'; +import { omit } from 'lodash'; import { KibanaSupertestProvider } from '@kbn/ftr-common-functional-services'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -45,14 +47,53 @@ export class SyntheticsMonitorTestService { return apiKey; }; - async getMonitor(monitorId: string, decrypted: boolean = true, space?: string) { - let url = - SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId) + - (decrypted ? '?decrypted=true' : ''); + async getMonitor( + monitorId: string, + { + statusCode = 200, + space, + internal, + }: { + statusCode?: number; + space?: string; + internal?: boolean; + } = {} + ) { + let url = SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId); if (space) { url = '/s/' + space + url; } - return this.supertest.get(url).set('kbn-xsrf', 'true').expect(200); + if (internal) { + url += `?internal=${internal}`; + } + const apiResponse = await this.supertest.get(url).expect(200); + + expect(apiResponse.status).eql(statusCode, JSON.stringify(apiResponse.body)); + + if (statusCode === 200) { + const { + created_at: createdAt, + updated_at: updatedAt, + id, + config_id: configId, + } = apiResponse.body; + expect(id).not.empty(); + expect(configId).not.empty(); + expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); + return { + rawBody: apiResponse.body, + body: { + ...omit(apiResponse.body, [ + 'created_at', + 'updated_at', + 'id', + 'config_id', + 'form_monitor_type', + ]), + }, + }; + } + return apiResponse.body; } async addMonitor(monitor: any) {