[Synthetics] Private location public API's (#169376)

Co-authored-by: Abdul Wahab Zahid <awahab07@yahoo.com>
Co-authored-by: Justin Kambic <jk@elastic.co>
This commit is contained in:
Shahzad 2023-11-06 18:29:27 +01:00 committed by GitHub
parent ed4ef2a3ff
commit d2a54d9180
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 586 additions and 237 deletions

View file

@ -0,0 +1,75 @@
[[create-private-location-api]]
== Create Private Location API
++++
<titleabbrev>Create Private Location</titleabbrev>
++++
Creates a private location with the following schema.
=== {api-request-title}
`POST <kibana host>:<port>/api/synthetics/private_locations`
`POST <kibana host>:<port>/s/<space_id>/api/synthetics/private_locations`
=== {api-prereq-title}
You must have `all` privileges for the *Synthetics and Uptime* feature in the *{observability}* section of the
<<kibana-feature-privileges,{kib} feature privileges>>.
[[private-location-request-body]]
==== Request body
The request body should contain the following attributes:
`label`::
(Required, string) A label for the private location.
`agentPolicyId`::
(Required, string) The ID of the agent policy associated with the private location.
`tags`::
(Optional, array of strings) An array of tags to categorize the private location.
`geo`::
(Optional, object) Geographic coordinates (WGS84) for the location. It should include the following attributes:
- `lat` (Required, number): The latitude of the location.
- `lon` (Required, number): The longitude of the location.
[[private-location-create-example]]
==== Example
Here is an example of a POST request to create a private location:
[source,sh]
--------------------------------------------------
POST /api/private_locations
{
"label": "Private Location 1",
"agentPolicyId": "abcd1234",
"tags": ["private", "testing"],
"geo": {
"lat": 40.7128,
"lon": -74.0060
}
}
--------------------------------------------------
The API returns the created private location as follows:
[source,json]
--------------------------------------------------
{
"id": "unique-location-id",
"label": "Private Location 1",
"agentPolicyId": "abcd1234",
"tags": ["private", "testing"],
"geo": {
"lat": 40.7128,
"lon": -74.0060
}
}
--------------------------------------------------
If the `agentPolicyId` is already used by an existing private location, or if the `label` already exists, the API will return a `400 Bad Request` response with a corresponding error message.

View file

@ -0,0 +1,41 @@
[[delete-private-location-api]]
== Delete Private Location API
++++
<titleabbrev>Delete Private Location</titleabbrev>
++++
Deletes a private location using the provided location ID.
=== {api-request-title}
`DELETE <kibana host>:<port>/api/synthetics/private_locations/<location_id>`
`DELETE <kibana host>:<port>/s/<space_id>/api/synthetics/private_locations/<location_id>`
=== {api-prereq-title}
You must have `all` privileges for the *Synthetics and Uptime* feature in the *{observability}* section of the
<<kibana-feature-privileges,{kib} feature privileges>>.
[[private-location-delete-params]]
==== Path Parameters
`location_id`::
(Required, string) The unique identifier of the private location to be deleted. It must be between 1 and 1024 characters.
[[private-location-delete-example]]
==== Example
Here is an example of a DELETE request to delete a private location:
[source,sh]
--------------------------------------------------
DELETE /api/private-locations/<location_id>
--------------------------------------------------
The API does not return a response body for deletion, but it will return an appropriate status code upon successful deletion.
This API will delete the private location with the specified `locationId`.
A location cannot be deleted if it has associated monitors in use. You must delete all monitors associated with the location before deleting the location.

View file

@ -0,0 +1,98 @@
[[get-private-locations-api]]
== Get Private Locations API
++++
<titleabbrev>Get Private Locations</titleabbrev>
++++
Retrieves a list of private locations or a single private location by ID.
=== {api-request-title}
`GET <kibana host>:<port>/api/synthetics/private_locations`
`GET <kibana host>:<port>/s/<space_id>/api/synthetics/private_locations`
=== {api-prereq-title}
You must have `read` privileges for the *Synthetics and Uptime* feature in the *{observability}* section of the
<<kibana-feature-privileges,{kib} feature privileges>>.
[[private-locations-list-response-example]]
==== List Response Example
The API returns a JSON array of private locations when accessing the list endpoint, with each private location having the following attributes:
- `label` (string): A label for the private location.
- `id` (string): The unique identifier of the private location.
- `agentPolicyId` (string): The ID of the agent policy associated with the private location.
- `isInvalid` (boolean): Indicates whether the location is invalid. If `true`, the location is invalid, which means the agent policy associated with the location is deleted.
- `geo` (object): Geographic coordinates for the location, including `lat` and `lon`.
- `namespace` (string): The namespace of the location, which is the same as the namespace of the agent policy associated with the location.
Here's an example list response:
[source,json]
--------------------------------------------------
[
{
"label": "Test private location",
"id": "fleet-server-policy",
"agentPolicyId": "fleet-server-policy",
"isInvalid": false,
"geo": {
"lat": 0,
"lon": 0
},
"namespace": "default"
},
{
"label": "Test private location 2",
"id": "691225b0-6ced-11ee-8f5a-376306ee85ae",
"agentPolicyId": "691225b0-6ced-11ee-8f5a-376306ee85ae",
"isInvalid": false,
"geo": {
"lat": 0,
"lon": 0
},
"namespace": "test"
}
]
--------------------------------------------------
[[private-location-by-id-response-example]]
==== Get by ID/Label Response Example
The API returns a JSON object of a single private location when accessing the endpoint with a specific `id`, with the same attributes as in the list response.
Here's an example request for a single location by ID:
[source,sh]
--------------------------------------------------
GET api/synthetics/private_locations/<location_id>
--------------------------------------------------
or by label:
[source,sh]
--------------------------------------------------
GET api/synthetics/private_locations/<Location label>
--------------------------------------------------
Here's an example response object:
[source,json]
--------------------------------------------------
{
"label": "Test private location",
"id": "test-private-location-id",
"agentPolicyId": "test-private-location-id",
"isServiceManaged": false,
"isInvalid": false,
"geo": {
"lat": 0,
"lon": 0
},
"namespace": "default"
}
--------------------------------------------------

View file

@ -11,8 +11,17 @@ The following APIs are available for Synthetics.
* <<delete-parameters-api, Delete Parameter API>> to delete a parameter.
* <<create-private-location-api, Create Private Location API>> to create a private location
* <<delete-private-location-api, Delete Private Location API>> to delete a private location
* <<get-private-locations-api, Get Private Locations API>> to get a list of private locations
include::params/add-param.asciidoc[leveloffset=+1]
include::params/get-params.asciidoc[leveloffset=+1]
include::params/edit-param.asciidoc[leveloffset=+1]
include::params/delete-param.asciidoc[leveloffset=+1]
include::private-locations/create-private-location.asciidoc[leveloffset=+1]
include::private-locations/delete-private-location.asciidoc[leveloffset=+1]
include::private-locations/get-private-locations.asciidoc[leveloffset=+1]

View file

@ -7,6 +7,7 @@
export enum SYNTHETICS_API_URLS {
// public apis
PRIVATE_LOCATIONS = `/api/synthetics/private_locations`,
PARAMS = `/api/synthetics/params`,
// Service end points
@ -28,7 +29,6 @@ export enum SYNTHETICS_API_URLS {
OVERVIEW_STATUS = `/internal/synthetics/overview_status`,
INDEX_SIZE = `/internal/synthetics/index_size`,
AGENT_POLICIES = `/internal/synthetics/agent_policies`,
PRIVATE_LOCATIONS = `/internal/synthetics/private_locations`,
PRIVATE_LOCATIONS_MONITORS = `/internal/synthetics/private_locations/monitors`,
SYNC_GLOBAL_PARAMS = `/internal/synthetics/sync_global_params`,
ENABLE_DEFAULT_ALERTING = `/internal/synthetics/enable_default_alerting`,

View file

@ -42,14 +42,11 @@ export type DynamicSettings = t.TypeOf<typeof DynamicSettingsCodec>;
export type DefaultEmail = t.TypeOf<typeof DefaultEmailCodec>;
export type DynamicSettingsSaveResponse = t.TypeOf<typeof DynamicSettingsSaveCodec>;
export const LocationMonitorsType = t.type({
status: t.number,
payload: t.array(
t.type({
id: t.string,
count: t.number,
})
),
});
export const LocationMonitorsType = t.array(
t.type({
id: t.string,
count: t.number,
})
);
export type LocationMonitorsResponse = t.TypeOf<typeof LocationMonitorsType>;

View file

@ -12,7 +12,6 @@ export const PrivateLocationCodec = t.intersection([
label: t.string,
id: t.string,
agentPolicyId: t.string,
concurrentMonitors: t.number,
}),
t.partial({
isServiceManaged: t.boolean,
@ -26,8 +25,6 @@ export const PrivateLocationCodec = t.intersection([
}),
]);
export const SyntheticsPrivateLocationsType = t.type({
locations: t.array(PrivateLocationCodec),
});
export const SyntheticsPrivateLocationsType = t.array(PrivateLocationCodec);
export type PrivateLocation = t.TypeOf<typeof PrivateLocationCodec>;
export type SyntheticsPrivateLocations = t.TypeOf<typeof SyntheticsPrivateLocationsType>;

View file

@ -32,9 +32,8 @@ import {
cleanMonitorListState,
} from '../../state';
import { MONITOR_ADD_ROUTE } from '../../../../../common/constants/ui';
import { PrivateLocation } from '../../../../../common/runtime_types';
import { SimpleMonitorForm } from './simple_monitor_form';
import { AddLocationFlyout } from '../settings/private_locations/add_location_flyout';
import { AddLocationFlyout, NewLocation } from '../settings/private_locations/add_location_flyout';
export const GettingStartedPage = () => {
const dispatch = useDispatch();
@ -111,7 +110,7 @@ export const GettingStartedOnPrem = () => {
const { onSubmit, privateLocations, loading } = usePrivateLocationsAPI();
const handleSubmit = (formData: PrivateLocation) => {
const handleSubmit = (formData: NewLocation) => {
onSubmit(formData);
};

View file

@ -26,6 +26,8 @@ import { PrivateLocation } from '../../../../../../common/runtime_types';
import { LocationForm } from './location_form';
import { ManageEmptyState } from './manage_empty_state';
export type NewLocation = Omit<PrivateLocation, 'id'>;
export const AddLocationFlyout = ({
onSubmit,
setIsOpen,
@ -33,7 +35,7 @@ export const AddLocationFlyout = ({
isLoading,
}: {
isLoading: boolean;
onSubmit: (val: PrivateLocation) => void;
onSubmit: (val: NewLocation) => void;
setIsOpen: (val: boolean) => void;
privateLocations: PrivateLocation[];
}) => {
@ -44,12 +46,10 @@ export const AddLocationFlyout = ({
defaultValues: {
label: '',
agentPolicyId: '',
id: '',
geo: {
lat: 0,
lon: 0,
},
concurrentMonitors: 1,
},
});

View file

@ -0,0 +1,34 @@
/*
* 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 React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonIcon, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
export const CopyName = ({ text }: { text: string }) => {
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiText>{text}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy textToCopy={text}>
{(copy) => (
<EuiButtonIcon
data-test-subj="syntheticsCopyNameButton"
color="text"
iconType="copy"
onClick={copy}
aria-label={i18n.translate('xpack.synthetics.copyName.copyNameButtonIconLabel', {
defaultMessage: 'Copy name',
})}
/>
)}
</EuiCopy>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -15,13 +15,11 @@ import * as reduxHooks from 'react-redux';
describe('usePrivateLocationsAPI', () => {
const dispatch = jest.fn();
const addAPI = jest.spyOn(locationAPI, 'addSyntheticsPrivateLocations').mockResolvedValue({
locations: [],
});
const deletedAPI = jest.spyOn(locationAPI, 'deleteSyntheticsPrivateLocations').mockResolvedValue({
locations: [],
});
const getAPI = jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations');
const addAPI = jest.spyOn(locationAPI, 'addSyntheticsPrivateLocations').mockResolvedValue([]);
const deletedAPI = jest
.spyOn(locationAPI, 'deleteSyntheticsPrivateLocations')
.mockResolvedValue([]);
jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations');
jest.spyOn(reduxHooks, 'useDispatch').mockReturnValue(dispatch);
it('returns expected results', () => {
@ -35,16 +33,14 @@ describe('usePrivateLocationsAPI', () => {
privateLocations: [],
})
);
expect(getAPI).toHaveBeenCalledTimes(1);
});
jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations').mockResolvedValue({
locations: [
{
id: 'Test',
agentPolicyId: 'testPolicy',
} as any,
],
expect(dispatch).toHaveBeenCalledTimes(1);
});
jest.spyOn(locationAPI, 'getSyntheticsPrivateLocations').mockResolvedValue([
{
id: 'Test',
agentPolicyId: 'testPolicy',
} as any,
]);
it('returns expected results after data', async () => {
const { result, waitForNextUpdate } = renderHook(() => usePrivateLocationsAPI(), {
wrapper: WrappedHelper,
@ -62,12 +58,7 @@ describe('usePrivateLocationsAPI', () => {
expect(result.current).toEqual(
expect.objectContaining({
loading: false,
privateLocations: [
{
id: 'Test',
agentPolicyId: 'testPolicy',
},
],
privateLocations: [],
})
);
});
@ -81,10 +72,8 @@ describe('usePrivateLocationsAPI', () => {
act(() => {
result.current.onSubmit({
id: 'new',
agentPolicyId: 'newPolicy',
label: 'new',
concurrentMonitors: 1,
geo: {
lat: 0,
lon: 0,
@ -95,8 +84,6 @@ describe('usePrivateLocationsAPI', () => {
await waitForNextUpdate();
expect(addAPI).toHaveBeenCalledWith({
concurrentMonitors: 1,
id: 'newPolicy',
geo: {
lat: 0,
lon: 0,

View file

@ -6,47 +6,47 @@
*/
import { useFetcher } from '@kbn/observability-shared-plugin/public';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { NewLocation } from '../add_location_flyout';
import { getServiceLocations } from '../../../../state/service_locations';
import { setAddingNewPrivateLocation } from '../../../../state/private_locations';
import {
getPrivateLocationsAction,
selectPrivateLocations,
selectPrivateLocationsLoading,
setAddingNewPrivateLocation,
} from '../../../../state/private_locations';
import {
addSyntheticsPrivateLocations,
deleteSyntheticsPrivateLocations,
getSyntheticsPrivateLocations,
} from '../../../../state/private_locations/api';
import { PrivateLocation } from '../../../../../../../common/runtime_types';
export const usePrivateLocationsAPI = () => {
const [formData, setFormData] = useState<PrivateLocation>();
const [formData, setFormData] = useState<NewLocation>();
const [deleteId, setDeleteId] = useState<string>();
const [privateLocations, setPrivateLocations] = useState<PrivateLocation[]>([]);
const dispatch = useDispatch();
const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val));
const privateLocations = useSelector(selectPrivateLocations);
const fetchLoading = useSelector(selectPrivateLocationsLoading);
const { loading: fetchLoading } = useFetcher(async () => {
const result = await getSyntheticsPrivateLocations();
setPrivateLocations(result.locations);
return result;
}, []);
useEffect(() => {
dispatch(getPrivateLocationsAction.get());
}, [dispatch]);
const { loading: saveLoading } = useFetcher(async () => {
if (formData) {
const result = await addSyntheticsPrivateLocations({
...formData,
id: formData.agentPolicyId,
});
setPrivateLocations(result.locations);
const result = await addSyntheticsPrivateLocations(formData);
setFormData(undefined);
setIsAddingNew(false);
dispatch(getServiceLocations());
dispatch(getPrivateLocationsAction.get());
return result;
}
}, [formData]);
const onSubmit = (data: PrivateLocation) => {
const onSubmit = (data: NewLocation) => {
setFormData(data);
};
@ -57,9 +57,9 @@ export const usePrivateLocationsAPI = () => {
const { loading: deleteLoading } = useFetcher(async () => {
if (deleteId) {
const result = await deleteSyntheticsPrivateLocations(deleteId);
setPrivateLocations(result.locations);
setDeleteId(undefined);
dispatch(getServiceLocations());
dispatch(getPrivateLocationsAction.get());
return result;
}
}, [deleteId]);

View file

@ -18,6 +18,7 @@ import {
import { i18n } from '@kbn/i18n';
import { useDispatch } from 'react-redux';
import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table';
import { CopyName } from './copy_name';
import { ViewLocationMonitors } from './view_location_monitors';
import { TableTitle } from '../../common/components/table_title';
import { TAGS_LABEL } from '../components/tags_field';
@ -62,6 +63,7 @@ export const PrivateLocationsTable = ({
{
field: 'label',
name: LOCATION_NAME_LABEL,
render: (label: string) => <CopyName text={label} />,
},
{
field: 'monitors',
@ -82,7 +84,7 @@ export const PrivateLocationsTable = ({
render: (val: string[]) => {
const tags = val ?? [];
if (tags.length === 0) {
return <EuiText>--</EuiText>;
return '--';
}
return (
<EuiFlexGroup gutterSize="xs" wrap>

View file

@ -121,7 +121,6 @@ describe('<ManagePrivateLocations />', () => {
id: 'lkjlere',
agentPolicyId: 'lkjelrje',
isServiceManaged: false,
concurrentMonitors: 2,
},
],
onDelete: jest.fn(),

View file

@ -9,14 +9,13 @@ import { useDispatch, useSelector } from 'react-redux';
import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout';
import { PrivateLocationsTable } from './locations_table';
import { ManageEmptyState } from './manage_empty_state';
import { AddLocationFlyout } from './add_location_flyout';
import { AddLocationFlyout, NewLocation } from './add_location_flyout';
import { usePrivateLocationsAPI } from './hooks/use_locations_api';
import {
getAgentPoliciesAction,
selectAddingNewPrivateLocation,
setAddingNewPrivateLocation,
} from '../../../state/private_locations';
import { PrivateLocation } from '../../../../../../common/runtime_types';
import { getServiceLocations } from '../../../state';
export const ManagePrivateLocations = () => {
@ -40,7 +39,7 @@ export const ManagePrivateLocations = () => {
dispatch(getServiceLocations());
}, [dispatch]);
const handleSubmit = (formData: PrivateLocation) => {
const handleSubmit = (formData: NewLocation) => {
onSubmit(formData);
};

View file

@ -6,6 +6,7 @@
*/
import { createAction } from '@reduxjs/toolkit';
import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
import { AgentPolicyInfo } from '../../../../../common/types';
import { createAsyncAction } from '../utils/actions';
@ -13,6 +14,10 @@ export const getAgentPoliciesAction = createAsyncAction<void, AgentPolicyInfo[]>
'[AGENT POLICIES] GET'
);
export const getPrivateLocationsAction = createAsyncAction<void, SyntheticsPrivateLocations>(
'[PRIVATE LOCATIONS] GET'
);
export const setManageFlyoutOpen = createAction<boolean>('SET MANAGE FLYOUT OPEN');
export const setAddingNewPrivateLocation = createAction<boolean>('SET MANAGE FLYOUT ADDING NEW');

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import { NewLocation } from '../../components/settings/private_locations/add_location_flyout';
import { AgentPolicyInfo } from '../../../../../common/types';
import { SYNTHETICS_API_URLS } from '../../../../../common/constants';
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
import { apiService } from '../../../../utils/api_service/api_service';
export const fetchAgentPolicies = async (): Promise<AgentPolicyInfo[]> => {
@ -15,17 +16,23 @@ export const fetchAgentPolicies = async (): Promise<AgentPolicyInfo[]> => {
};
export const addSyntheticsPrivateLocations = async (
newLocation: PrivateLocation
newLocation: NewLocation
): Promise<SyntheticsPrivateLocations> => {
return await apiService.post(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, newLocation);
return await apiService.post(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, newLocation, undefined, {
version: INITIAL_REST_VERSION,
});
};
export const getSyntheticsPrivateLocations = async (): Promise<SyntheticsPrivateLocations> => {
return await apiService.get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS);
return await apiService.get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS, {
version: INITIAL_REST_VERSION,
});
};
export const deleteSyntheticsPrivateLocations = async (
locationId: string
): Promise<SyntheticsPrivateLocations> => {
return await apiService.delete(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + `/${locationId}`);
return await apiService.delete(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + `/${locationId}`, {
version: INITIAL_REST_VERSION,
});
};

View file

@ -7,8 +7,8 @@
import { takeLeading } from 'redux-saga/effects';
import { fetchEffectFactory } from '../utils/fetch_effect';
import { fetchAgentPolicies } from './api';
import { getAgentPoliciesAction } from './actions';
import { fetchAgentPolicies, getSyntheticsPrivateLocations } from './api';
import { getAgentPoliciesAction, getPrivateLocationsAction } from './actions';
export function* fetchAgentPoliciesEffect() {
yield takeLeading(
@ -20,3 +20,14 @@ export function* fetchAgentPoliciesEffect() {
)
);
}
export function* fetchPrivateLocationsEffect() {
yield takeLeading(
getPrivateLocationsAction.get,
fetchEffectFactory(
getSyntheticsPrivateLocations,
getPrivateLocationsAction.success,
getPrivateLocationsAction.fail
)
);
}

View file

@ -6,13 +6,20 @@
*/
import { createReducer } from '@reduxjs/toolkit';
import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types';
import { AgentPolicyInfo } from '../../../../../common/types';
import { IHttpSerializedFetchError } from '..';
import { getAgentPoliciesAction, setAddingNewPrivateLocation } from './actions';
import {
getAgentPoliciesAction,
setAddingNewPrivateLocation,
getPrivateLocationsAction,
} from './actions';
export interface AgentPoliciesState {
data: AgentPolicyInfo[] | null;
privateLocations?: SyntheticsPrivateLocations | null;
loading: boolean;
fetchLoading?: boolean;
error: IHttpSerializedFetchError | null;
isManageFlyoutOpen?: boolean;
isAddingNewPrivateLocation?: boolean;
@ -39,6 +46,17 @@ export const agentPoliciesReducer = createReducer(initialState, (builder) => {
state.error = action.payload;
state.loading = false;
})
.addCase(getPrivateLocationsAction.get, (state) => {
state.fetchLoading = true;
})
.addCase(getPrivateLocationsAction.success, (state, action) => {
state.privateLocations = action.payload;
state.fetchLoading = false;
})
.addCase(getPrivateLocationsAction.fail, (state, action) => {
state.error = action.payload;
state.fetchLoading = false;
})
.addCase(setAddingNewPrivateLocation, (state, action) => {
state.isAddingNewPrivateLocation = action.payload;
});

View file

@ -13,3 +13,9 @@ export const selectAgentPolicies = createSelector(getState, (state) => state);
export const selectAddingNewPrivateLocation = (state: AppState) =>
state.agentPolicies.isAddingNewPrivateLocation ?? false;
export const selectPrivateLocationsLoading = (state: AppState) =>
state.agentPolicies.fetchLoading ?? false;
export const selectPrivateLocations = (state: AppState) =>
state.agentPolicies.privateLocations ?? [];

View file

@ -23,7 +23,7 @@ import {
setDynamicSettingsEffect,
} from './settings/effects';
import { syncGlobalParamsEffect } from './settings';
import { fetchAgentPoliciesEffect } from './private_locations';
import { fetchAgentPoliciesEffect, fetchPrivateLocationsEffect } from './private_locations';
import { fetchNetworkEventsEffect } from './network_events/effects';
import { fetchSyntheticsMonitorEffect } from './monitor_details';
import { fetchSyntheticsEnablementEffect } from './synthetics_enablement';
@ -53,6 +53,7 @@ export const rootEffect = function* root(): Generator {
fork(fetchNetworkEventsEffect),
fork(fetchPingStatusesEffect),
fork(fetchAgentPoliciesEffect),
fork(fetchPrivateLocationsEffect),
fork(fetchDynamicSettingsEffect),
fork(fetchLocationMonitorsEffect),
fork(setDynamicSettingsEffect),

View file

@ -12,9 +12,9 @@ import {
import { apiService } from '../../../../utils/api_service';
import {
DynamicSettings,
DynamicSettingsSaveResponse,
DynamicSettingsSaveCodec,
DynamicSettingsCodec,
DynamicSettingsSaveCodec,
DynamicSettingsSaveResponse,
LocationMonitorsResponse,
LocationMonitorsType,
} from '../../../../../common/runtime_types';
@ -47,13 +47,11 @@ export const setDynamicSettings = async ({
};
export const fetchLocationMonitors = async (): Promise<LocationMonitor[]> => {
const { payload } = await apiService.get<LocationMonitorsResponse>(
return await apiService.get<LocationMonitorsResponse>(
SYNTHETICS_API_URLS.PRIVATE_LOCATIONS_MONITORS,
undefined,
LocationMonitorsType
);
return payload;
};
export type ActionConnector = Omit<RawActionConnector, 'secrets'>;

View file

@ -90,10 +90,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
createNetworkEventsRoute,
createJourneyScreenshotRoute,
deletePackagePolicyRoute,
addPrivateLocationRoute,
deletePrivateLocationRoute,
getLocationMonitors,
getPrivateLocationsRoute,
getSyntheticsFilters,
inspectSyntheticsMonitorRoute,
getAgentPoliciesRoute,
@ -107,4 +104,7 @@ export const syntheticsAppPublicRestApiRoutes: SyntheticsRestApiRouteFactory[] =
editSyntheticsParamsRoute,
addSyntheticsParamsRoute,
deleteSyntheticsParamsRoute,
addPrivateLocationRoute,
deletePrivateLocationRoute,
getPrivateLocationsRoute,
];

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { schema } from '@kbn/config-schema';
import { schema, TypeOf } from '@kbn/config-schema';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { getPrivateLocationsAndAgentPolicies } from './get_private_locations';
import {
@ -13,15 +13,13 @@ import {
privateLocationsSavedObjectName,
} from '../../../../common/saved_objects/private_locations';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
import type { PrivateLocation, SyntheticsPrivateLocations } from '../../../../common/runtime_types';
import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations';
import { toClientContract, toSavedObjectContract } from './helpers';
import { PrivateLocation } from '../../../../common/runtime_types';
export const PrivateLocationSchema = schema.object({
label: schema.string(),
id: schema.string(),
agentPolicyId: schema.string(),
concurrentMonitors: schema.number(),
tags: schema.maybe(schema.arrayOf(schema.string())),
geo: schema.maybe(
schema.object({
@ -31,25 +29,57 @@ export const PrivateLocationSchema = schema.object({
),
});
export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory<
SyntheticsPrivateLocations
> = () => ({
export type PrivateLocationObject = TypeOf<typeof PrivateLocationSchema>;
export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory<PrivateLocation> = () => ({
method: 'POST',
path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS,
validate: {
body: PrivateLocationSchema,
validate: {},
validation: {
request: {
body: PrivateLocationSchema,
},
},
writeAccess: true,
handler: async ({ request, savedObjectsClient, syntheticsMonitorClient }) => {
const location = request.body as PrivateLocation;
handler: async ({ response, request, savedObjectsClient, syntheticsMonitorClient }) => {
const location = request.body as PrivateLocationObject;
const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies(
savedObjectsClient,
syntheticsMonitorClient
);
if (locations.find((loc) => loc.agentPolicyId === location.agentPolicyId)) {
return response.badRequest({
body: {
message: `Private location with agentPolicyId ${location.agentPolicyId} already exists`,
},
});
}
// return if name is already taken
if (locations.find((loc) => loc.label === location.label)) {
return response.badRequest({
body: {
message: `Private location with label ${location.label} already exists`,
},
});
}
const existingLocations = locations.filter((loc) => loc.id !== location.agentPolicyId);
const formattedLocation = toSavedObjectContract(location);
const formattedLocation = toSavedObjectContract({
...location,
id: location.agentPolicyId,
});
const agentPolicy = agentPolicies?.find((policy) => policy.id === location.agentPolicyId);
if (!agentPolicy) {
return response.badRequest({
body: {
message: `Agent policy with id ${location.agentPolicyId} does not exist`,
},
});
}
const result = await savedObjectsClient.create<SyntheticsPrivateLocationsAttributes>(
privateLocationsSavedObjectName,
@ -60,6 +90,8 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory<
}
);
return toClientContract(result.attributes, agentPolicies);
const allLocations = toClientContract(result.attributes, agentPolicies);
return allLocations.find((loc) => loc.id === location.agentPolicyId)!;
},
});

View file

@ -6,6 +6,8 @@
*/
import { schema } from '@kbn/config-schema';
import { isEmpty } from 'lodash';
import { getMonitorsByLocation } from './get_location_monitors';
import { getPrivateLocationsAndAgentPolicies } from './get_private_locations';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
@ -13,31 +15,50 @@ import {
privateLocationsSavedObjectId,
privateLocationsSavedObjectName,
} from '../../../../common/saved_objects/private_locations';
import type { SyntheticsPrivateLocations } from '../../../../common/runtime_types';
import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations';
import { toClientContract } from './helpers';
export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory<
SyntheticsPrivateLocations
> = () => ({
export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory<undefined> = () => ({
method: 'DELETE',
path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + '/{locationId}',
validate: {
params: schema.object({
locationId: schema.string({ minLength: 1, maxLength: 1024 }),
}),
validate: {},
validation: {
request: {
params: schema.object({
locationId: schema.string({ minLength: 1, maxLength: 1024 }),
}),
},
},
writeAccess: true,
handler: async ({ savedObjectsClient, syntheticsMonitorClient, request }) => {
handler: async ({ response, savedObjectsClient, syntheticsMonitorClient, request, server }) => {
const { locationId } = request.params as { locationId: string };
const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies(
const { locations } = await getPrivateLocationsAndAgentPolicies(
savedObjectsClient,
syntheticsMonitorClient
);
if (!locations.find((loc) => loc.id === locationId)) {
return response.badRequest({
body: {
message: `Private location with id ${locationId} does not exist.`,
},
});
}
const monitors = await getMonitorsByLocation(server, locationId);
if (!isEmpty(monitors)) {
const count = monitors.find((monitor) => monitor.id === locationId)?.count;
return response.badRequest({
body: {
message: `Private location with id ${locationId} cannot be deleted because it is used by ${count} monitor(s).`,
},
});
}
const remainingLocations = locations.filter((loc) => loc.id !== locationId);
const result = await savedObjectsClient.create<SyntheticsPrivateLocationsAttributes>(
await savedObjectsClient.create<SyntheticsPrivateLocationsAttributes>(
privateLocationsSavedObjectName,
{ locations: remainingLocations },
{
@ -46,6 +67,6 @@ export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory<
}
);
return toClientContract(result.attributes, agentPolicies);
return;
},
});

View file

@ -5,10 +5,12 @@
* 2.0.
*/
import { IKibanaResponse } from '@kbn/core/server';
import { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants';
import { getKqlFilter } from '../../common';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
import { monitorAttributes, syntheticsMonitorType } from '../../../../common/types/saved_objects';
import { SyntheticsServerSetup } from '../../../types';
type Payload = Array<{
id: string;
@ -33,28 +35,32 @@ const aggs = {
},
};
export const getLocationMonitors: SyntheticsRestApiRouteFactory = () => ({
export const getLocationMonitors: SyntheticsRestApiRouteFactory<Payload> = () => ({
method: 'GET',
path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS_MONITORS,
validate: {},
handler: async ({ savedObjectsClient: soClient }): Promise<IKibanaResponse<Payload>> => {
const locationMonitors = await soClient?.find<unknown, ExpectedResponse>({
type: syntheticsMonitorType,
perPage: 0,
aggs,
});
const payload =
locationMonitors.aggregations?.locations.buckets.map(({ key: id, doc_count: count }) => ({
id,
count,
})) ?? [];
return {
options: {},
payload,
status: 200,
};
handler: async ({ server }) => {
return await getMonitorsByLocation(server);
},
});
export const getMonitorsByLocation = async (server: SyntheticsServerSetup, locationId?: string) => {
const soClient = server.coreStart.savedObjects.createInternalRepository();
const locationFilter = getKqlFilter({ field: 'locations.id', values: locationId });
const locationMonitors = await soClient.find<unknown, ExpectedResponse>({
type: syntheticsMonitorType,
perPage: 0,
aggs,
filter: locationFilter,
namespaces: [ALL_SPACES_ID],
});
return (
locationMonitors.aggregations?.locations.buckets.map(({ key: id, doc_count: count }) => ({
id,
count,
})) ?? []
);
};

View file

@ -6,9 +6,10 @@
*/
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { schema } from '@kbn/config-schema';
import { AgentPolicyInfo } from '../../../../common/types';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { SyntheticsPrivateLocations } from '../../../../common/runtime_types';
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../common/runtime_types';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
import { getPrivateLocations } from '../../../synthetics_service/get_private_locations';
import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations';
@ -16,17 +17,36 @@ import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_
import { toClientContract } from './helpers';
export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory<
SyntheticsPrivateLocations
SyntheticsPrivateLocations | PrivateLocation
> = () => ({
method: 'GET',
path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS,
path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS + '/{id?}',
validate: {},
handler: async ({ savedObjectsClient, syntheticsMonitorClient }) => {
validation: {
request: {
params: schema.object({
id: schema.maybe(schema.string()),
}),
},
},
handler: async ({ savedObjectsClient, syntheticsMonitorClient, request, response }) => {
const { id } = request.params as { id?: string };
const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies(
savedObjectsClient,
syntheticsMonitorClient
);
return toClientContract({ locations }, agentPolicies);
const list = toClientContract({ locations }, agentPolicies);
if (!id) return list;
const location = list.find((loc) => loc.id === id || loc.label === id);
if (!location) {
return response.notFound({
body: {
message: `Private location with id or label "${id}" not found`,
},
});
}
return location;
},
});

View file

@ -14,7 +14,6 @@ const testLocations = {
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
geo: { lat: 0, lon: 0 },
concurrentMonitors: 1,
isInvalid: false,
isServiceManaged: false,
tags: ['a tag 2'],
@ -24,7 +23,6 @@ const testLocations = {
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728dec',
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
geo: { lat: '', lon: '' },
concurrentMonitors: 1,
isInvalid: true,
isServiceManaged: true,
tags: ['a tag'],
@ -39,7 +37,6 @@ const testLocations2 = {
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
geo: { lat: -10, lon: 20 },
concurrentMonitors: 1,
isInvalid: false,
isServiceManaged: false,
tags: ['a tag 2'],
@ -49,7 +46,6 @@ const testLocations2 = {
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728dec',
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
geo: { lat: -10, lon: 20 },
concurrentMonitors: 1,
isInvalid: true,
isServiceManaged: true,
tags: ['a tag'],
@ -60,69 +56,61 @@ const testLocations2 = {
describe('toClientContract', () => {
it('formats SO attributes to client contract with falsy geo location', () => {
// @ts-ignore fixtures are purposely wrong types for testing
expect(toClientContract(testLocations)).toEqual({
locations: [
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
concurrentMonitors: 1,
geo: {
lat: 0,
lon: 0,
},
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag 2'],
expect(toClientContract(testLocations)).toEqual([
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
geo: {
lat: 0,
lon: 0,
},
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728dec',
concurrentMonitors: 1,
geo: {
lat: '',
lon: '',
},
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag'],
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag 2'],
},
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728dec',
geo: {
lat: '',
lon: '',
},
],
});
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag'],
},
]);
});
it('formats SO attributes to client contract with truthy geo location', () => {
// @ts-ignore fixtures are purposely wrong types for testing
expect(toClientContract(testLocations2)).toEqual({
locations: [
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
concurrentMonitors: 1,
geo: {
lat: -10,
lon: 20,
},
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag 2'],
expect(toClientContract(testLocations2)).toEqual([
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
geo: {
lat: -10,
lon: 20,
},
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728dec',
concurrentMonitors: 1,
geo: {
lat: -10,
lon: 20,
},
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag'],
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag 2'],
},
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728dec',
geo: {
lat: -10,
lon: 20,
},
],
});
id: 'e3134290-0f73-11ee-ba15-159f4f728dec',
isInvalid: true,
isServiceManaged: false,
label: 'BEEP',
tags: ['a tag'],
},
]);
});
});

View file

@ -16,22 +16,19 @@ export const toClientContract = (
attributes: SyntheticsPrivateLocationsAttributes,
agentPolicies?: AgentPolicyInfo[]
): SyntheticsPrivateLocations => {
return {
locations: attributes.locations.map((location) => {
const agPolicy = agentPolicies?.find((policy) => policy.id === location.agentPolicyId);
return {
label: location.label,
id: location.id,
agentPolicyId: location.agentPolicyId,
concurrentMonitors: location.concurrentMonitors,
isServiceManaged: false,
isInvalid: !Boolean(agPolicy),
tags: location.tags,
geo: location.geo,
namespace: agPolicy?.namespace,
};
}),
};
return attributes.locations.map((location) => {
const agPolicy = agentPolicies?.find((policy) => policy.id === location.agentPolicyId);
return {
label: location.label,
id: location.id,
agentPolicyId: location.agentPolicyId,
isServiceManaged: false,
isInvalid: !Boolean(agPolicy),
tags: location.tags,
geo: location.geo,
namespace: agPolicy?.namespace,
};
});
};
export const toSavedObjectContract = (location: PrivateLocation): PrivateLocationAttributes => {
@ -39,7 +36,6 @@ export const toSavedObjectContract = (location: PrivateLocation): PrivateLocatio
label: location.label,
id: location.id,
agentPolicyId: location.agentPolicyId,
concurrentMonitors: location.concurrentMonitors,
tags: location.tags,
isServiceManaged: false,
geo: location.geo,

View file

@ -42,7 +42,7 @@ export const getServiceLocationsRoute: SyntheticsRestApiRouteFactory = () => ({
const { locations: privateLocations, agentPolicies } =
await getPrivateLocationsAndAgentPolicies(savedObjectsClient, syntheticsMonitorClient);
const result = toClientContract({ locations: privateLocations }, agentPolicies).locations;
const result = toClientContract({ locations: privateLocations }, agentPolicies);
return {
locations: result,
};

View file

@ -12,7 +12,6 @@ export const PrivateLocationAttributesCodec = t.intersection([
label: t.string,
id: t.string,
agentPolicyId: t.string,
concurrentMonitors: t.number,
isServiceManaged: t.boolean,
}),
t.partial({

View file

@ -34,7 +34,7 @@ export async function getAllLocations({
throttling,
allLocations: [
...publicLocations,
...toClientContract({ locations: privateLocations }, agentPolicies).locations,
...toClientContract({ locations: privateLocations }, agentPolicies),
],
};
} catch (e) {

View file

@ -25,7 +25,6 @@ describe('SyntheticsPrivateLocation', () => {
const mockPrivateLocation: PrivateLocationAttributes = {
id: 'policyId',
label: 'Test Location',
concurrentMonitors: 1,
agentPolicyId: 'policyId',
isServiceManaged: false,
};

View file

@ -53,7 +53,6 @@ describe('browser normalizers', () => {
id: 'germany',
label: 'Germany',
isServiceManaged: false,
concurrentMonitors: 1,
agentPolicyId: 'germany',
},
];

View file

@ -37,7 +37,6 @@ describe('http normalizers', () => {
id: 'germany',
label: 'Germany',
isServiceManaged: false,
concurrentMonitors: 1,
agentPolicyId: 'germany',
},
];

View file

@ -37,7 +37,6 @@ describe('icmp normalizers', () => {
id: 'germany',
label: 'Germany',
isServiceManaged: false,
concurrentMonitors: 1,
agentPolicyId: 'germany',
},
];

View file

@ -37,7 +37,6 @@ describe('tcp normalizers', () => {
id: 'germany',
label: 'Germany',
isServiceManaged: false,
concurrentMonitors: 1,
agentPolicyId: 'germany',
},
];

View file

@ -89,7 +89,6 @@ const privateLocations = times(1).map((n) => {
},
isServiceManaged: false,
agentPolicyId: `loc-${n}`,
concurrentMonitors: 1,
};
}) as PrivateLocation[];

View file

@ -97,7 +97,6 @@ describe('SyntheticsMonitorClient', () => {
},
isServiceManaged: false,
agentPolicyId: `loc-${n}`,
concurrentMonitors: 1,
};
});

View file

@ -7,7 +7,13 @@
import moment from 'moment';
import semver from 'semver';
import { v4 as uuidv4 } from 'uuid';
import { ConfigKey, HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types';
import {
ConfigKey,
HTTPFields,
LocationStatus,
PrivateLocation,
ServiceLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters';
import { omit } from 'lodash';
@ -60,18 +66,17 @@ export default function ({ getService }: FtrProviderContext) {
const apiResponse = await supertestAPI.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS);
expect(apiResponse.body.locations).eql([
const testResponse: Array<PrivateLocation | ServiceLocation> = [
{
id: 'localhost',
label: 'Local Synthetics Service',
geo: { lat: 0, lon: 0 },
url: 'mockDevUrl',
isServiceManaged: true,
status: 'experimental',
status: LocationStatus.EXPERIMENTAL,
isInvalid: false,
},
{
concurrentMonitors: 1,
id: testFleetPolicyID,
isServiceManaged: false,
isInvalid: false,
@ -83,7 +88,9 @@ export default function ({ getService }: FtrProviderContext) {
agentPolicyId: testFleetPolicyID,
namespace: 'default',
},
]);
];
expect(apiResponse.body.locations).eql(testResponse);
});
it('does not add a monitor if there is an error in creating integration', async () => {

View file

@ -7,6 +7,7 @@
import { v4 as uuidv4 } from 'uuid';
import { privateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations';
import { privateLocationsSavedObjectId } from '@kbn/synthetics-plugin/server/saved_objects/private_locations';
import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtime_types';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { KibanaSupertestProvider } from '../../../../../../test/api_integration/services/supertest';
@ -58,7 +59,7 @@ export class PrivateLocationTestService {
async setTestLocations(testFleetPolicyIds: string[]) {
const server = this.getService('kibanaServer');
const locations = testFleetPolicyIds.map((id, index) => ({
const locations: SyntheticsPrivateLocations = testFleetPolicyIds.map((id, index) => ({
label: 'Test private location ' + index,
agentPolicyId: id,
id,
@ -66,7 +67,6 @@ export class PrivateLocationTestService {
lat: 0,
lon: 0,
},
concurrentMonitors: 1,
isServiceManaged: false,
}));

View file

@ -8,6 +8,9 @@ import moment from 'moment';
import {
ConfigKey,
HTTPFields,
LocationStatus,
PrivateLocation,
ServiceLocation,
SyntheticsParams,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
@ -66,18 +69,17 @@ export default function ({ getService }: FtrProviderContext) {
const apiResponse = await supertestAPI.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS);
expect(apiResponse.body.locations).eql([
const testLocations: Array<PrivateLocation | ServiceLocation> = [
{
id: 'localhost',
label: 'Local Synthetics Service',
geo: { lat: 0, lon: 0 },
url: 'mockDevUrl',
isServiceManaged: true,
status: 'experimental',
status: LocationStatus.EXPERIMENTAL,
isInvalid: false,
},
{
concurrentMonitors: 1,
id: testFleetPolicyID,
isInvalid: false,
isServiceManaged: false,
@ -89,7 +91,9 @@ export default function ({ getService }: FtrProviderContext) {
agentPolicyId: testFleetPolicyID,
namespace: 'default',
},
]);
];
expect(apiResponse.body.locations).eql(testLocations);
});
it('adds a monitor in private location', async () => {