diff --git a/x-pack/plugins/synthetics/common/config.ts b/x-pack/plugins/synthetics/common/config.ts index 02cb1cdc5f256..5b0e56fed4423 100644 --- a/x-pack/plugins/synthetics/common/config.ts +++ b/x-pack/plugins/synthetics/common/config.ts @@ -18,6 +18,7 @@ const serviceConfig = schema.object({ syncInterval: schema.maybe(schema.string()), tls: schema.maybe(sslSchema), devUrl: schema.maybe(schema.string()), + showExperimentalLocations: schema.maybe(schema.boolean()), }); const uptimeConfig = schema.object({ diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts index 8ee664bda1ebf..c7b12b526d64e 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts @@ -9,6 +9,11 @@ import { isLeft } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; import { tEnum } from '../../utils/t_enum'; +export enum LocationStatus { + GA = 'ga', + EXPERIMENTAL = 'experimental', +} + export enum BandwidthLimitKey { DOWNLOAD = 'download', UPLOAD = 'upload', @@ -36,13 +41,16 @@ export const LocationGeoCodec = t.interface({ lon: t.number, }); +export const LocationStatusCodec = tEnum('LocationStatus', LocationStatus); +export type LocationStatusType = t.TypeOf; + export const ManifestLocationCodec = t.interface({ url: t.string, geo: t.interface({ name: t.string, location: LocationGeoCodec, }), - status: t.string, + status: LocationStatusCodec, }); export const ServiceLocationCodec = t.interface({ @@ -51,6 +59,7 @@ export const ServiceLocationCodec = t.interface({ geo: LocationGeoCodec, url: t.string, isServiceManaged: t.boolean, + status: LocationStatusCodec, }); export const MonitorServiceLocationCodec = t.intersection([ diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.test.tsx index 7cf8f0a8204a1..d748475734380 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.test.tsx @@ -7,8 +7,10 @@ import React from 'react'; import { fireEvent, screen } from '@testing-library/react'; +import { within } from '@testing-library/dom'; import { render } from '../../../lib/helper/rtl_helpers'; import { ServiceLocations } from './locations'; +import { LocationStatus } from '../../../../../common/runtime_types'; describe('', () => { const setLocations = jest.fn(); @@ -94,4 +96,35 @@ describe('', () => { fireEvent.blur(checkbox); expect(onBlur).toHaveBeenCalledTimes(1); }); + + it('shows experimental badges next to experimental locations', () => { + const multiLocations = [ + { ...location, id: 'L1', label: 'first', status: LocationStatus.EXPERIMENTAL }, + { ...location, id: 'L2', label: 'second', status: LocationStatus.GA }, + { ...location, id: 'L3', label: 'third', status: LocationStatus.EXPERIMENTAL }, + { ...location, id: 'L4', label: 'fourth', status: LocationStatus.GA }, + ]; + + const { getByTestId } = render( + , + { + state: { + monitorManagementList: { ...state.monitorManagementList, locations: multiLocations }, + }, + } + ); + + multiLocations.forEach((expectedLocation) => { + const locationText = getByTestId(`syntheticsServiceLocationText--${expectedLocation.id}`); + + within(locationText).getByText(expectedLocation.label); + + if (expectedLocation.status !== LocationStatus.GA) { + within(locationText).getByText('Tech Preview'); + } else { + const techPreviewBadge = within(locationText).queryByText('Tech Preview'); + expect(techPreviewBadge).not.toBeInTheDocument(); + } + }); + }); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx index 029503196b6a3..fe2461b98e83a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/locations.tsx @@ -8,9 +8,9 @@ import React, { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; -import { EuiCheckboxGroup, EuiFormRow } from '@elastic/eui'; +import { EuiCheckboxGroup, EuiFormRow, EuiText, EuiBadge } from '@elastic/eui'; import { monitorManagementListSelector } from '../../../state/selectors'; -import { MonitorServiceLocations } from '../../../../../common/runtime_types'; +import { MonitorServiceLocations, LocationStatus } from '../../../../../common/runtime_types'; interface Props { selectedLocations: MonitorServiceLocations; @@ -57,10 +57,22 @@ export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid, o return ( ({ - ...location, - 'data-test-subj': `syntheticsServiceLocation--${location.id}`, - }))} + options={locations.map((location) => { + const badge = + location.status !== LocationStatus.GA ? ( + Tech Preview + ) : null; + const label = ( + + {location.label} {badge} + + ); + return { + ...location, + label, + 'data-test-subj': `syntheticsServiceLocation--${location.id}`, + }; + })} idToSelectedMap={checkboxIdToSelectedMap} onChange={(id) => onLocationChange(id)} onBlur={() => onBlur?.()} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx index 2592aabf9eb09..592d2c906c19d 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/monitor_async_error.test.tsx @@ -7,7 +7,7 @@ import { screen } from '@testing-library/react'; import React from 'react'; -import { DEFAULT_THROTTLING } from '../../../../../common/runtime_types'; +import { DEFAULT_THROTTLING, LocationStatus } from '../../../../../common/runtime_types'; import { render } from '../../../lib/helper/rtl_helpers'; import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; import { MonitorAsyncError } from './monitor_async_error'; @@ -55,6 +55,7 @@ describe('', () => { }, url: '', isServiceManaged: true, + status: LocationStatus.GA, }, { id: 'us_north', @@ -65,6 +66,7 @@ describe('', () => { }, url: '', isServiceManaged: true, + status: LocationStatus.GA, }, ], error: { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.test.tsx index b040f7a27da33..f77a926cf097f 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/test_now_mode/test_now_mode.test.tsx @@ -10,7 +10,7 @@ import { screen } from '@testing-library/react'; import { render } from '../../../lib/helper/rtl_helpers'; import { TestNowMode } from './test_now_mode'; import { kibanaService } from '../../../state/kibana_service'; -import { Locations, MonitorFields } from '../../../../../common/runtime_types'; +import { Locations, MonitorFields, LocationStatus } from '../../../../../common/runtime_types'; import * as runOnceErrorHooks from '../hooks/use_run_once_errors'; describe('TestNowMode', function () { @@ -21,6 +21,7 @@ describe('TestNowMode', function () { geo: { lat: 33.333, lon: 73.333 }, url: 'test-url', isServiceManaged: true, + status: LocationStatus.GA, }, ]; const testMonitor = { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts index 71b611e44d399..f429f17a36615 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.test.ts @@ -6,7 +6,8 @@ */ import axios from 'axios'; import { getServiceLocations } from './get_service_locations'; -import { BandwidthLimitKey } from '../../common/runtime_types'; + +import { BandwidthLimitKey, LocationStatus } from '../../common/runtime_types'; jest.mock('axios'); const mockedAxios = axios as jest.Mocked; @@ -26,42 +27,151 @@ describe('getServiceLocations', function () { name: 'US Central', location: { lat: 41.25, lon: -95.86 }, }, - status: 'beta', + status: LocationStatus.GA, + }, + us_east: { + url: 'https://local.dev', + geo: { + name: 'US East', + location: { lat: 41.25, lon: -95.86 }, + }, + status: LocationStatus.EXPERIMENTAL, }, }, }, }); - it('should return parsed locations and throttling', async () => { - const locations = await getServiceLocations({ - config: { - service: { - manifestUrl: 'http://local.dev', + describe('when out of production', () => { + it('should return all locations regardless of the `showExperimentalLocations` key', async () => { + const locations = await getServiceLocations({ + isDev: true, + config: { + service: { + manifestUrl: 'http://local.dev', + showExperimentalLocations: false, + }, }, - }, - // @ts-ignore - logger: { - error: jest.fn(), - }, + // @ts-ignore + logger: { + error: jest.fn(), + }, + }); + + expect(locations).toEqual({ + throttling: { + [BandwidthLimitKey.DOWNLOAD]: 100, + [BandwidthLimitKey.UPLOAD]: 50, + }, + locations: [ + { + geo: { + lat: 41.25, + lon: -95.86, + }, + id: 'us_central', + label: 'US Central', + url: 'https://local.dev', + isServiceManaged: true, + status: LocationStatus.GA, + }, + { + geo: { + lat: 41.25, + lon: -95.86, + }, + id: 'us_east', + label: 'US East', + url: 'https://local.dev', + isServiceManaged: true, + status: LocationStatus.EXPERIMENTAL, + }, + ], + }); }); + }); - expect(locations).toEqual({ - throttling: { - [BandwidthLimitKey.DOWNLOAD]: 100, - [BandwidthLimitKey.UPLOAD]: 50, - }, - locations: [ - { - geo: { - lat: 41.25, - lon: -95.86, + describe('when in production', () => { + it('should return only GA locations and throttling when `showExperimentalLocations` is set to false', async () => { + const locations = await getServiceLocations({ + isDev: false, + config: { + service: { + manifestUrl: 'http://local.dev', + showExperimentalLocations: false, }, - id: 'us_central', - label: 'US Central', - url: 'https://local.dev', - isServiceManaged: true, }, - ], + // @ts-ignore + logger: { + error: jest.fn(), + }, + }); + + expect(locations).toEqual({ + throttling: { + [BandwidthLimitKey.DOWNLOAD]: 100, + [BandwidthLimitKey.UPLOAD]: 50, + }, + locations: [ + { + geo: { + lat: 41.25, + lon: -95.86, + }, + id: 'us_central', + label: 'US Central', + url: 'https://local.dev', + isServiceManaged: true, + status: LocationStatus.GA, + }, + ], + }); + }); + + it('should return all locations and throttling when `showExperimentalLocations` flag is set to true', async () => { + const locations = await getServiceLocations({ + isDev: false, + config: { + service: { + manifestUrl: 'http://local.dev', + showExperimentalLocations: true, + }, + }, + // @ts-ignore + logger: { + error: jest.fn(), + }, + }); + + expect(locations).toEqual({ + throttling: { + [BandwidthLimitKey.DOWNLOAD]: 100, + [BandwidthLimitKey.UPLOAD]: 50, + }, + locations: [ + { + geo: { + lat: 41.25, + lon: -95.86, + }, + id: 'us_central', + label: 'US Central', + url: 'https://local.dev', + isServiceManaged: true, + status: LocationStatus.GA, + }, + { + geo: { + lat: 41.25, + lon: -95.86, + }, + id: 'us_east', + label: 'US East', + url: 'https://local.dev', + isServiceManaged: true, + status: LocationStatus.EXPERIMENTAL, + }, + ], + }); }); }); }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.ts index 53ecdfa324ca7..5919d636357b3 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_service_locations.ts @@ -13,6 +13,7 @@ import { Locations, ThrottlingOptions, BandwidthLimitKey, + LocationStatus, } from '../../common/runtime_types'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters/framework'; @@ -22,6 +23,7 @@ export const getDevLocation = (devUrl: string): ServiceLocation => ({ geo: { lat: 0, lon: 0 }, url: devUrl, isServiceManaged: true, + status: LocationStatus.EXPERIMENTAL, }); export async function getServiceLocations(server: UptimeServerSetup) { @@ -41,13 +43,21 @@ export async function getServiceLocations(server: UptimeServerSetup) { locations: Record; }>(server.config.service!.manifestUrl!); - Object.entries(data.locations).forEach(([locationId, location]) => { + const availableLocations = + server.isDev || server.config.service?.showExperimentalLocations + ? Object.entries(data.locations) + : Object.entries(data.locations).filter(([_, location]) => { + return location.status === LocationStatus.GA; + }); + + availableLocations.forEach(([locationId, location]) => { locations.push({ id: locationId, label: location.geo.name, geo: location.geo.location, url: location.url, isServiceManaged: true, + status: location.status, }); }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts index 6654fdbf2a132..6f8aa3a521f3c 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -13,6 +13,7 @@ import { loggerMock } from '@kbn/core/server/logging/logger.mock'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import axios, { AxiosResponse } from 'axios'; import times from 'lodash/times'; +import { LocationStatus } from '../../common/runtime_types'; describe('SyntheticsService', () => { const mockEsClient = { @@ -45,6 +46,7 @@ describe('SyntheticsService', () => { lon: 0, }, isServiceManaged: true, + status: LocationStatus.GA, }; }); @@ -122,6 +124,7 @@ describe('SyntheticsService', () => { label: 'Local Synthetics Service', url: 'http://localhost', isServiceManaged: true, + status: LocationStatus.EXPERIMENTAL, }, ]); });