mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
ed4ef2a3ff
commit
d2a54d9180
42 changed files with 586 additions and 237 deletions
|
@ -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.
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
||||
--------------------------------------------------
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -121,7 +121,6 @@ describe('<ManagePrivateLocations />', () => {
|
|||
id: 'lkjlere',
|
||||
agentPolicyId: 'lkjelrje',
|
||||
isServiceManaged: false,
|
||||
concurrentMonitors: 2,
|
||||
},
|
||||
],
|
||||
onDelete: jest.fn(),
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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 ?? [];
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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'>;
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
|
|
@ -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)!;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
})) ?? []
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -34,7 +34,7 @@ export async function getAllLocations({
|
|||
throttling,
|
||||
allLocations: [
|
||||
...publicLocations,
|
||||
...toClientContract({ locations: privateLocations }, agentPolicies).locations,
|
||||
...toClientContract({ locations: privateLocations }, agentPolicies),
|
||||
],
|
||||
};
|
||||
} catch (e) {
|
||||
|
|
|
@ -25,7 +25,6 @@ describe('SyntheticsPrivateLocation', () => {
|
|||
const mockPrivateLocation: PrivateLocationAttributes = {
|
||||
id: 'policyId',
|
||||
label: 'Test Location',
|
||||
concurrentMonitors: 1,
|
||||
agentPolicyId: 'policyId',
|
||||
isServiceManaged: false,
|
||||
};
|
||||
|
|
|
@ -53,7 +53,6 @@ describe('browser normalizers', () => {
|
|||
id: 'germany',
|
||||
label: 'Germany',
|
||||
isServiceManaged: false,
|
||||
concurrentMonitors: 1,
|
||||
agentPolicyId: 'germany',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -37,7 +37,6 @@ describe('http normalizers', () => {
|
|||
id: 'germany',
|
||||
label: 'Germany',
|
||||
isServiceManaged: false,
|
||||
concurrentMonitors: 1,
|
||||
agentPolicyId: 'germany',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -37,7 +37,6 @@ describe('icmp normalizers', () => {
|
|||
id: 'germany',
|
||||
label: 'Germany',
|
||||
isServiceManaged: false,
|
||||
concurrentMonitors: 1,
|
||||
agentPolicyId: 'germany',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -37,7 +37,6 @@ describe('tcp normalizers', () => {
|
|||
id: 'germany',
|
||||
label: 'Germany',
|
||||
isServiceManaged: false,
|
||||
concurrentMonitors: 1,
|
||||
agentPolicyId: 'germany',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -89,7 +89,6 @@ const privateLocations = times(1).map((n) => {
|
|||
},
|
||||
isServiceManaged: false,
|
||||
agentPolicyId: `loc-${n}`,
|
||||
concurrentMonitors: 1,
|
||||
};
|
||||
}) as PrivateLocation[];
|
||||
|
||||
|
|
|
@ -97,7 +97,6 @@ describe('SyntheticsMonitorClient', () => {
|
|||
},
|
||||
isServiceManaged: false,
|
||||
agentPolicyId: `loc-${n}`,
|
||||
concurrentMonitors: 1,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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,
|
||||
}));
|
||||
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue