> to delete a parameter.
+
+
+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]
diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc
index 4358c448f3634aa..7731a65baaac7d9 100644
--- a/docs/user/api.asciidoc
+++ b/docs/user/api.asciidoc
@@ -109,4 +109,5 @@ include::{kib-repo-dir}/api/osquery-manager.asciidoc[]
include::{kib-repo-dir}/api/short-urls.asciidoc[]
include::{kib-repo-dir}/api/task-manager/health.asciidoc[]
include::{kib-repo-dir}/api/upgrade-assistant.asciidoc[]
+include::{kib-repo-dir}/api/synthetics/synthetics-api.asciidoc[]
include::{kib-repo-dir}/api/uptime-api.asciidoc[]
diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts
index 40ae2656e2b26c7..6f7a1855ac4025c 100644
--- a/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts
+++ b/x-pack/plugins/synthetics/common/constants/synthetics/rest_api.ts
@@ -6,6 +6,9 @@
*/
export enum SYNTHETICS_API_URLS {
+ // public apis
+ PARAMS = `/api/synthetics/params`,
+
// Service end points
INDEX_TEMPLATES = '/internal/synthetics/service/index_templates',
SERVICE_LOCATIONS = '/internal/uptime/service/locations',
@@ -24,7 +27,6 @@ export enum SYNTHETICS_API_URLS {
PING_STATUSES = '/internal/synthetics/ping_statuses',
OVERVIEW_STATUS = `/internal/synthetics/overview_status`,
INDEX_SIZE = `/internal/synthetics/index_size`,
- PARAMS = `/internal/synthetics/params`,
AGENT_POLICIES = `/internal/synthetics/agent_policies`,
PRIVATE_LOCATIONS = `/internal/synthetics/private_locations`,
PRIVATE_LOCATIONS_MONITORS = `/internal/synthetics/private_locations/monitors`,
diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts
index 64259a676189c65..8f3ee8f3b903b26 100644
--- a/x-pack/plugins/synthetics/common/constants/ui.ts
+++ b/x-pack/plugins/synthetics/common/constants/ui.ts
@@ -73,3 +73,5 @@ export const SYNTHETICS_INDEX_PATTERN = 'synthetics-*';
export const LICENSE_NOT_ACTIVE_ERROR = 'License not active';
export const LICENSE_MISSING_ERROR = 'Missing license information';
export const LICENSE_NOT_SUPPORTED_ERROR = 'License not supported';
+
+export const INITIAL_REST_VERSION = '2023-10-31';
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx
index c79e5aec2177a1b..3fd17335d2ea5f9 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/add_param_flyout.tsx
@@ -108,8 +108,9 @@ export const AddParamFlyout = ({
useEffect(() => {
if (isEditingItem) {
+ const { id: _id, ...dataToEdit } = isEditingItem;
setIsFlyoutVisible(true);
- form.reset(isEditingItem);
+ form.reset(dataToEdit);
}
// no need to add form value, it keeps changing on reset
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx
index aa89829380044c7..942afb91cfe48cb 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx
@@ -55,7 +55,7 @@ export const DeleteParam = ({
{' '}
{i18n.translate('xpack.synthetics.paramManagement.paramDeleteFailuresMessage.name', {
- defaultMessage: 'Param {name} deleted successfully.',
+ defaultMessage: 'Param {name} failed to delete.',
values: { name },
})}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts
index 528921f1d5bf4cc..1499734639a33e2 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/global_params/api.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { SYNTHETICS_API_URLS } from '../../../../../common/constants';
+import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
import {
DeleteParamsResponse,
SyntheticsParamRequest,
@@ -18,7 +18,7 @@ import { apiService } from '../../../../utils/api_service/api_service';
export const getGlobalParams = async (): Promise => {
return apiService.get(
SYNTHETICS_API_URLS.PARAMS,
- undefined,
+ { version: INITIAL_REST_VERSION },
SyntheticsParamsReadonlyCodec
);
};
@@ -26,7 +26,9 @@ export const getGlobalParams = async (): Promise => {
export const addGlobalParam = async (
paramRequest: SyntheticsParamRequest
): Promise =>
- apiService.post(SYNTHETICS_API_URLS.PARAMS, paramRequest, SyntheticsParamsCodec);
+ apiService.post(SYNTHETICS_API_URLS.PARAMS, paramRequest, SyntheticsParamsCodec, {
+ version: INITIAL_REST_VERSION,
+ });
export const editGlobalParam = async ({
paramRequest,
@@ -36,15 +38,15 @@ export const editGlobalParam = async ({
paramRequest: SyntheticsParamRequest;
}): Promise =>
apiService.put(
- SYNTHETICS_API_URLS.PARAMS,
+ SYNTHETICS_API_URLS.PARAMS + `/${id}`,
+ paramRequest,
+ SyntheticsParamsCodec,
{
- id,
- ...paramRequest,
- },
- SyntheticsParamsCodec
+ version: INITIAL_REST_VERSION,
+ }
);
export const deleteGlobalParams = async (ids: string[]): Promise =>
- apiService.delete(SYNTHETICS_API_URLS.PARAMS, {
- ids: JSON.stringify(ids),
+ apiService.delete(SYNTHETICS_API_URLS.PARAMS, undefined, {
+ ids,
});
diff --git a/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts b/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts
index f1eb2607dd25b98..e077e37d0983557 100644
--- a/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts
+++ b/x-pack/plugins/synthetics/public/utils/api_service/api_service.ts
@@ -101,8 +101,15 @@ class ApiService {
return this.parseResponse(response, apiUrl, decodeType);
}
- public async delete(apiUrl: string, params?: HttpFetchQuery) {
- const response = await this._http!.delete({ path: apiUrl, query: params });
+ public async delete(apiUrl: string, params: Params = {}, data?: any) {
+ const { version, ...queryParams } = params;
+
+ const response = await this._http!.delete({
+ path: apiUrl,
+ query: queryParams,
+ body: JSON.stringify(data),
+ version,
+ });
if (response instanceof Error) {
throw response;
diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts
index accad22a12817cb..1c02627eddcd77c 100644
--- a/x-pack/plugins/synthetics/server/routes/index.ts
+++ b/x-pack/plugins/synthetics/server/routes/index.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { getSyntheticsParamsRoute } from './settings/params/params';
+import { editSyntheticsParamsRoute } from './settings/params/edit_param';
import { getConnectorTypesRoute } from './default_alerts/get_connector_types';
import { getActionConnectorsRoute } from './default_alerts/get_action_connectors';
import { SyntheticsRestApiRouteFactory } from './types';
@@ -18,8 +20,6 @@ import { createLastSuccessfulCheckRoute } from './pings/last_successful_check';
import { createJourneyFailedStepsRoute, createJourneyRoute } from './pings/journeys';
import { updateDefaultAlertingRoute } from './default_alerts/update_default_alert';
import { syncParamsSyntheticsParamsRoute } from './settings/sync_global_params';
-import { editSyntheticsParamsRoute } from './settings/edit_param';
-import { getSyntheticsParamsRoute } from './settings/params';
import { getIndexSizesRoute } from './settings/settings';
import { getAPIKeySyntheticsRoute } from './monitor_cruds/get_api_key';
import { getServiceLocationsRoute } from './synthetics_service/get_service_locations';
@@ -44,8 +44,6 @@ import { addSyntheticsProjectMonitorRoute } from './monitor_cruds/add_monitor_pr
import { syntheticsGetPingsRoute, syntheticsGetPingStatusesRoute } from './pings';
import { createGetCurrentStatusRoute } from './overview_status/overview_status';
import { getHasIntegrationMonitorsRoute } from './fleet/get_has_integration_monitors';
-import { addSyntheticsParamsRoute } from './settings/add_param';
-import { deleteSyntheticsParamsRoute } from './settings/delete_param';
import { enableDefaultAlertingRoute } from './default_alerts/enable_default_alert';
import { getDefaultAlertingRoute } from './default_alerts/get_default_alert';
import { createNetworkEventsRoute } from './network_events';
@@ -55,6 +53,8 @@ import { getPrivateLocationsRoute } from './settings/private_locations/get_priva
import { getSyntheticsFilters } from './filters/filters';
import { getAllSyntheticsMonitorRoute } from './monitor_cruds/get_monitors_list';
import { getLocationMonitors } from './settings/private_locations/get_location_monitors';
+import { addSyntheticsParamsRoute } from './settings/params/add_param';
+import { deleteSyntheticsParamsRoute } from './settings/params/delete_param';
export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
addSyntheticsMonitorRoute,
@@ -79,10 +79,6 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
getHasIntegrationMonitorsRoute,
createGetCurrentStatusRoute,
getIndexSizesRoute,
- getSyntheticsParamsRoute,
- editSyntheticsParamsRoute,
- addSyntheticsParamsRoute,
- deleteSyntheticsParamsRoute,
syncParamsSyntheticsParamsRoute,
enableDefaultAlertingRoute,
getDefaultAlertingRoute,
@@ -105,3 +101,10 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
getActionConnectorsRoute,
getConnectorTypesRoute,
];
+
+export const syntheticsAppPublicRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
+ getSyntheticsParamsRoute,
+ editSyntheticsParamsRoute,
+ addSyntheticsParamsRoute,
+ deleteSyntheticsParamsRoute,
+];
diff --git a/x-pack/plugins/synthetics/server/routes/settings/add_param.ts b/x-pack/plugins/synthetics/server/routes/settings/add_param.ts
deleted file mode 100644
index 875e4da8694e13d..000000000000000
--- a/x-pack/plugins/synthetics/server/routes/settings/add_param.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { schema } from '@kbn/config-schema';
-import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants';
-import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
-import { IKibanaResponse } from '@kbn/core/server';
-import { SyntheticsRestApiRouteFactory } from '../types';
-import {
- SyntheticsParamRequest,
- SyntheticsParams,
- SyntheticsParamSOAttributes,
-} from '../../../common/runtime_types';
-import { syntheticsParamType } from '../../../common/types/saved_objects';
-import { SYNTHETICS_API_URLS } from '../../../common/constants';
-
-export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({
- method: 'POST',
- path: SYNTHETICS_API_URLS.PARAMS,
- validate: {
- body: schema.object({
- key: schema.string(),
- value: schema.string(),
- description: schema.maybe(schema.string()),
- tags: schema.maybe(schema.arrayOf(schema.string())),
- share_across_spaces: schema.maybe(schema.boolean()),
- }),
- },
- writeAccess: true,
- handler: async ({
- request,
- response,
- server,
- savedObjectsClient,
- }): Promise> => {
- try {
- const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? {
- id: DEFAULT_SPACE_ID,
- };
- const { share_across_spaces: shareAcrossSpaces, ...data } =
- request.body as SyntheticsParamRequest;
-
- const {
- attributes: { key, tags, description },
- id,
- namespaces,
- } = await savedObjectsClient.create>(
- syntheticsParamType,
- data,
- {
- initialNamespaces: shareAcrossSpaces ? [ALL_SPACES_ID] : [spaceId],
- }
- );
- return response.ok({
- body: {
- id,
- description,
- key,
- namespaces,
- tags,
- value: data.value,
- },
- });
- } catch (error) {
- if (error.output?.statusCode === 404) {
- const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
- return response.notFound({
- body: { message: `Kibana space '${spaceId}' does not exist` },
- });
- }
-
- throw error;
- }
- },
-});
diff --git a/x-pack/plugins/synthetics/server/routes/settings/delete_param.ts b/x-pack/plugins/synthetics/server/routes/settings/delete_param.ts
deleted file mode 100644
index dc529902b730f66..000000000000000
--- a/x-pack/plugins/synthetics/server/routes/settings/delete_param.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { IKibanaResponse } from '@kbn/core/server';
-import { schema } from '@kbn/config-schema';
-import { SyntheticsRestApiRouteFactory } from '../types';
-import { syntheticsParamType } from '../../../common/types/saved_objects';
-import { SYNTHETICS_API_URLS } from '../../../common/constants';
-import { DeleteParamsResponse } from '../../../common/runtime_types';
-
-export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({
- method: 'DELETE',
- path: SYNTHETICS_API_URLS.PARAMS,
- validate: {
- query: schema.object({
- ids: schema.string(),
- }),
- },
- writeAccess: true,
- handler: async ({
- savedObjectsClient,
- request,
- response,
- }): Promise> => {
- const { ids } = request.query as { ids: string };
- const parsedIds = JSON.parse(ids) as string[];
-
- const result = await savedObjectsClient.bulkDelete(
- parsedIds.map((id) => ({ type: syntheticsParamType, id })),
- { force: true }
- );
- return response.ok({
- body: result.statuses.map(({ id, success }) => ({ id, deleted: success })),
- });
- },
-});
diff --git a/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts b/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts
deleted file mode 100644
index b235d0b323c8b65..000000000000000
--- a/x-pack/plugins/synthetics/server/routes/settings/edit_param.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { schema } from '@kbn/config-schema';
-import { IKibanaResponse, SavedObject } from '@kbn/core/server';
-import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
-import { SyntheticsRestApiRouteFactory } from '../types';
-import { SyntheticsParamRequest, SyntheticsParams } from '../../../common/runtime_types';
-import { syntheticsParamType } from '../../../common/types/saved_objects';
-import { SYNTHETICS_API_URLS } from '../../../common/constants';
-
-export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({
- method: 'PUT',
- path: SYNTHETICS_API_URLS.PARAMS,
- validate: {
- body: schema.object({
- id: schema.string(),
- key: schema.string(),
- value: schema.string(),
- description: schema.maybe(schema.string()),
- tags: schema.maybe(schema.arrayOf(schema.string())),
- share_across_spaces: schema.maybe(schema.boolean()),
- }),
- },
- writeAccess: true,
- handler: async ({
- savedObjectsClient,
- request,
- response,
- server,
- }): Promise> => {
- try {
- const { id: _spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? {
- id: DEFAULT_SPACE_ID,
- };
- const {
- share_across_spaces: shareAcrossSpaces,
- id,
- ...data
- } = request.body as SyntheticsParamRequest & {
- id: string;
- };
-
- const { value } = data;
- const {
- id: responseId,
- attributes: { key, tags, description },
- namespaces,
- } = (await savedObjectsClient.update(
- syntheticsParamType,
- id,
- data
- )) as SavedObject;
-
- return response.ok({ body: { id: responseId, key, tags, description, namespaces, value } });
- } catch (error) {
- if (error.output?.statusCode === 404) {
- const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
- return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } });
- }
-
- throw error;
- }
- },
-});
diff --git a/x-pack/plugins/synthetics/server/routes/settings/params.ts b/x-pack/plugins/synthetics/server/routes/settings/params.ts
deleted file mode 100644
index 789761c529e846b..000000000000000
--- a/x-pack/plugins/synthetics/server/routes/settings/params.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { IKibanaResponse } from '@kbn/core/server';
-import { SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server';
-import { SyntheticsRestApiRouteFactory } from '../types';
-import { syntheticsParamType } from '../../../common/types/saved_objects';
-import { SYNTHETICS_API_URLS } from '../../../common/constants';
-import { SyntheticsParams, SyntheticsParamsReadonly } from '../../../common/runtime_types';
-
-type SyntheticsParamsResponse =
- | IKibanaResponse
- | IKibanaResponse;
-export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
- SyntheticsParamsResponse
-> = () => ({
- method: 'GET',
- path: SYNTHETICS_API_URLS.PARAMS,
- validate: {},
- handler: async ({ savedObjectsClient, request, response, server, spaceId }) => {
- try {
- const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
-
- const canSave =
- (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime.save ?? false;
-
- if (canSave) {
- const finder =
- await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser(
- {
- type: syntheticsParamType,
- perPage: 1000,
- namespaces: [spaceId],
- }
- );
-
- const hits: Array> = [];
- for await (const result of finder.find()) {
- hits.push(...result.saved_objects);
- }
-
- return response.ok({
- body: hits.map(({ id, attributes, namespaces }) => ({
- ...attributes,
- id,
- namespaces,
- })),
- });
- } else {
- const data = await savedObjectsClient.find({
- type: syntheticsParamType,
- perPage: 10000,
- });
- return response.ok({
- body: data.saved_objects.map(({ id, attributes, namespaces }) => ({
- ...attributes,
- namespaces,
- id,
- })),
- });
- }
- } catch (error) {
- if (error.output?.statusCode === 404) {
- return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } });
- }
-
- throw error;
- }
- },
-});
diff --git a/x-pack/plugins/synthetics/server/routes/settings/params/add_param.ts b/x-pack/plugins/synthetics/server/routes/settings/params/add_param.ts
new file mode 100644
index 000000000000000..9e26666ed30d900
--- /dev/null
+++ b/x-pack/plugins/synthetics/server/routes/settings/params/add_param.ts
@@ -0,0 +1,113 @@
+/*
+ * 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 { schema } from '@kbn/config-schema';
+import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants';
+import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
+import { SavedObject, SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server';
+import { SyntheticsRestApiRouteFactory } from '../../types';
+import {
+ SyntheticsParamRequest,
+ SyntheticsParams,
+ SyntheticsParamSOAttributes,
+} from '../../../../common/runtime_types';
+import { syntheticsParamType } from '../../../../common/types/saved_objects';
+import { SYNTHETICS_API_URLS } from '../../../../common/constants';
+
+const ParamsObjectSchema = schema.object({
+ key: schema.string(),
+ value: schema.string(),
+ description: schema.maybe(schema.string()),
+ tags: schema.maybe(schema.arrayOf(schema.string())),
+ share_across_spaces: schema.maybe(schema.boolean()),
+});
+
+export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
+ SyntheticsParams | SyntheticsParams[]
+> = () => ({
+ method: 'POST',
+ path: SYNTHETICS_API_URLS.PARAMS,
+ validate: {},
+ validation: {
+ request: {
+ body: schema.oneOf([ParamsObjectSchema, schema.arrayOf(ParamsObjectSchema)]),
+ },
+ },
+ writeAccess: true,
+ handler: async ({ request, response, server, savedObjectsClient }) => {
+ try {
+ const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? {
+ id: DEFAULT_SPACE_ID,
+ };
+
+ const savedObjectsData = parseParamBody(
+ spaceId,
+ request.body as SyntheticsParamRequest[] | SyntheticsParamRequest
+ );
+
+ const result = await savedObjectsClient.bulkCreate>(
+ savedObjectsData
+ );
+
+ if (savedObjectsData.length > 1) {
+ return result.saved_objects.map((savedObject) => {
+ return toClientResponse(savedObject);
+ });
+ } else {
+ return toClientResponse(result.saved_objects[0]);
+ }
+ } catch (error) {
+ if (error.output?.statusCode === 404) {
+ const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
+ return response.notFound({
+ body: { message: `Kibana space '${spaceId}' does not exist` },
+ });
+ }
+
+ throw error;
+ }
+ },
+});
+
+const toClientResponse = (savedObject: SavedObject>) => {
+ const { id, attributes: data, namespaces } = savedObject;
+ const { description, key, tags } = data;
+ return {
+ id,
+ description,
+ key,
+ namespaces,
+ tags,
+ value: data.value,
+ };
+};
+
+const parseParamBody = (
+ spaceId: string,
+ body: SyntheticsParamRequest[] | SyntheticsParamRequest
+): Array>> => {
+ if (Array.isArray(body)) {
+ const params = body as SyntheticsParamRequest[];
+ return params.map((param) => {
+ const { share_across_spaces: shareAcrossSpaces, ...data } = param;
+ return {
+ type: syntheticsParamType,
+ attributes: data,
+ initialNamespaces: shareAcrossSpaces ? [ALL_SPACES_ID] : [spaceId],
+ };
+ });
+ }
+
+ const { share_across_spaces: shareAcrossSpaces, ...data } = body;
+ return [
+ {
+ type: syntheticsParamType,
+ attributes: data,
+ initialNamespaces: shareAcrossSpaces ? [ALL_SPACES_ID] : [spaceId],
+ },
+ ];
+};
diff --git a/x-pack/plugins/synthetics/server/routes/settings/params/delete_param.ts b/x-pack/plugins/synthetics/server/routes/settings/params/delete_param.ts
new file mode 100644
index 000000000000000..f0f377ce82d9e55
--- /dev/null
+++ b/x-pack/plugins/synthetics/server/routes/settings/params/delete_param.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { schema } from '@kbn/config-schema';
+import { SyntheticsRestApiRouteFactory } from '../../types';
+import { syntheticsParamType } from '../../../../common/types/saved_objects';
+import { SYNTHETICS_API_URLS } from '../../../../common/constants';
+import { DeleteParamsResponse } from '../../../../common/runtime_types';
+
+export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
+ DeleteParamsResponse[],
+ unknown,
+ unknown,
+ { ids: string[] }
+> = () => ({
+ method: 'DELETE',
+ path: SYNTHETICS_API_URLS.PARAMS,
+ validate: {},
+ validation: {
+ request: {
+ body: schema.object({
+ ids: schema.arrayOf(schema.string()),
+ }),
+ },
+ },
+ writeAccess: true,
+ handler: async ({ savedObjectsClient, request }) => {
+ const { ids } = request.body;
+
+ const result = await savedObjectsClient.bulkDelete(
+ ids.map((id) => ({ type: syntheticsParamType, id })),
+ { force: true }
+ );
+ return result.statuses.map(({ id, success }) => ({ id, deleted: success }));
+ },
+});
diff --git a/x-pack/plugins/synthetics/server/routes/settings/params/edit_param.ts b/x-pack/plugins/synthetics/server/routes/settings/params/edit_param.ts
new file mode 100644
index 000000000000000..cd1b0731eedb0d1
--- /dev/null
+++ b/x-pack/plugins/synthetics/server/routes/settings/params/edit_param.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { schema, TypeOf } from '@kbn/config-schema';
+import { SavedObject } from '@kbn/core/server';
+import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
+import { SyntheticsRestApiRouteFactory } from '../../types';
+import { SyntheticsParamRequest, SyntheticsParams } from '../../../../common/runtime_types';
+import { syntheticsParamType } from '../../../../common/types/saved_objects';
+import { SYNTHETICS_API_URLS } from '../../../../common/constants';
+
+const RequestParamsSchema = schema.object({
+ id: schema.string(),
+});
+
+type RequestParams = TypeOf;
+
+export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
+ SyntheticsParams,
+ RequestParams
+> = () => ({
+ method: 'PUT',
+ path: SYNTHETICS_API_URLS.PARAMS + '/{id}',
+ validate: {},
+ validation: {
+ request: {
+ params: RequestParamsSchema,
+ body: schema.object({
+ key: schema.string(),
+ value: schema.string(),
+ description: schema.maybe(schema.string()),
+ tags: schema.maybe(schema.arrayOf(schema.string())),
+ share_across_spaces: schema.maybe(schema.boolean()),
+ }),
+ },
+ },
+ writeAccess: true,
+ handler: async ({ savedObjectsClient, request, server, response }) => {
+ try {
+ const { id: _spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? {
+ id: DEFAULT_SPACE_ID,
+ };
+ const { id } = request.params;
+ const { share_across_spaces: _shareAcrossSpaces, ...data } =
+ request.body as SyntheticsParamRequest & {
+ id: string;
+ };
+
+ const { value } = data;
+ const {
+ id: responseId,
+ attributes: { key, tags, description },
+ namespaces,
+ } = (await savedObjectsClient.update(
+ syntheticsParamType,
+ id,
+ data
+ )) as SavedObject;
+
+ return { id: responseId, key, tags, description, namespaces, value };
+ } catch (error) {
+ if (error.output?.statusCode === 404) {
+ const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
+ return response.notFound({
+ body: { message: `Kibana space '${spaceId}' does not exist` },
+ });
+ }
+
+ throw error;
+ }
+ },
+});
diff --git a/x-pack/plugins/synthetics/server/routes/settings/params/params.ts b/x-pack/plugins/synthetics/server/routes/settings/params/params.ts
new file mode 100644
index 000000000000000..8f9f4f76efd8975
--- /dev/null
+++ b/x-pack/plugins/synthetics/server/routes/settings/params/params.ts
@@ -0,0 +1,102 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { SavedObject, SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server';
+import { schema, TypeOf } from '@kbn/config-schema';
+import { SyntheticsRestApiRouteFactory } from '../../types';
+import { syntheticsParamType } from '../../../../common/types/saved_objects';
+import { SYNTHETICS_API_URLS } from '../../../../common/constants';
+import { SyntheticsParams, SyntheticsParamsReadonly } from '../../../../common/runtime_types';
+
+const RequestParamsSchema = schema.object({
+ id: schema.maybe(schema.string()),
+});
+
+type RequestParams = TypeOf;
+
+export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
+ SyntheticsParams[] | SyntheticsParamsReadonly[] | SyntheticsParams | SyntheticsParamsReadonly,
+ RequestParams
+> = () => ({
+ method: 'GET',
+ path: SYNTHETICS_API_URLS.PARAMS + '/{id?}',
+ validate: {},
+ validation: {
+ request: {
+ params: RequestParamsSchema,
+ },
+ },
+ handler: async ({ savedObjectsClient, request, response, server, spaceId }) => {
+ try {
+ const { id: paramId } = request.params;
+
+ const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
+
+ const canSave =
+ (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime.save ?? false;
+
+ if (canSave) {
+ if (paramId) {
+ const savedObject =
+ await encryptedSavedObjectsClient.getDecryptedAsInternalUser(
+ syntheticsParamType,
+ paramId,
+ { namespace: spaceId }
+ );
+ return toClientResponse(savedObject);
+ }
+
+ const finder =
+ await encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser(
+ {
+ type: syntheticsParamType,
+ perPage: 1000,
+ namespaces: [spaceId],
+ }
+ );
+
+ const hits: Array> = [];
+ for await (const result of finder.find()) {
+ hits.push(...result.saved_objects);
+ }
+
+ return hits.map((savedObject) => toClientResponse(savedObject));
+ } else {
+ if (paramId) {
+ const savedObject = await savedObjectsClient.get(
+ syntheticsParamType,
+ paramId
+ );
+ return toClientResponse(savedObject);
+ }
+
+ const data = await savedObjectsClient.find({
+ type: syntheticsParamType,
+ perPage: 10000,
+ });
+ return data.saved_objects.map((savedObject) => toClientResponse(savedObject));
+ }
+ } catch (error) {
+ if (error.output?.statusCode === 404) {
+ return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } });
+ }
+
+ throw error;
+ }
+ },
+});
+
+const toClientResponse = (
+ savedObject: SavedObject
+) => {
+ const { id, attributes, namespaces } = savedObject;
+ return {
+ ...attributes,
+ id,
+ namespaces,
+ };
+};
diff --git a/x-pack/plugins/synthetics/server/routes/types.ts b/x-pack/plugins/synthetics/server/routes/types.ts
index b372c6fd11a7f64..7224a6a9e7f5af1 100644
--- a/x-pack/plugins/synthetics/server/routes/types.ts
+++ b/x-pack/plugins/synthetics/server/routes/types.ts
@@ -16,6 +16,7 @@ import {
KibanaResponseFactory,
IKibanaResponse,
} from '@kbn/core/server';
+import { FullValidationConfig } from '@kbn/core-http-server';
import { UptimeEsClient } from '../lib';
import { SyntheticsServerSetup, UptimeRequestHandlerContext } from '../types';
import { SyntheticsMonitorClient } from '../synthetics_service/synthetics_monitor/synthetics_monitor_client';
@@ -32,6 +33,7 @@ export interface UMServerRoute {
method: 'GET' | 'PUT' | 'POST' | 'DELETE';
writeAccess?: boolean;
handler: T;
+ validation?: FullValidationConfig;
streamHandler?: (
context: UptimeRequestHandlerContext,
request: SyntheticsRequest,
@@ -57,13 +59,17 @@ export type UMKibanaRoute = UMRouteDefinition<
export type SyntheticsRestApiRouteFactory<
ClientContract = any,
- QueryParams = Record
-> = () => SyntheticsRoute;
+ Params = any,
+ Query = Record,
+ Body = any
+> = () => SyntheticsRoute;
export type SyntheticsRoute<
ClientContract = unknown,
- QueryParams = Record
-> = UMRouteDefinition>;
+ Params = Record,
+ Query = Record,
+ Body = any
+> = UMRouteDefinition>;
export type SyntheticsRouteWrapper = (
uptimeRoute: SyntheticsRoute>,
@@ -81,10 +87,14 @@ export interface UptimeRouteContext {
subject?: Subject;
}
-export interface RouteContext> {
+export interface RouteContext<
+ Params = Record,
+ Query = Record,
+ Body = any
+> {
uptimeEsClient: UptimeEsClient;
context: UptimeRequestHandlerContext;
- request: KibanaRequest, Query, Record>;
+ request: KibanaRequest;
response: KibanaResponseFactory;
savedObjectsClient: SavedObjectsClientContract;
server: SyntheticsServerSetup;
@@ -93,7 +103,12 @@ export interface RouteContext> {
spaceId: string;
}
-export type SyntheticsRouteHandler> = ({
+export type SyntheticsRouteHandler<
+ ClientContract,
+ Params = Record,
+ Query = Record,
+ Body = any
+> = ({
uptimeEsClient,
context,
request,
@@ -101,4 +116,4 @@ export type SyntheticsRouteHandler) => Promise | ClientContract>;
+}: RouteContext) => Promise | ClientContract>;
diff --git a/x-pack/plugins/synthetics/server/server.ts b/x-pack/plugins/synthetics/server/server.ts
index 9e829045013d4a1..69a1aa79d2410e1 100644
--- a/x-pack/plugins/synthetics/server/server.ts
+++ b/x-pack/plugins/synthetics/server/server.ts
@@ -11,7 +11,7 @@ import { SyntheticsPluginsSetupDependencies, SyntheticsServerSetup } from './typ
import { createSyntheticsRouteWithAuth } from './routes/create_route_with_auth';
import { SyntheticsMonitorClient } from './synthetics_service/synthetics_monitor/synthetics_monitor_client';
import { syntheticsRouteWrapper } from './synthetics_route_wrapper';
-import { syntheticsAppRestApiRoutes } from './routes';
+import { syntheticsAppPublicRestApiRoutes, syntheticsAppRestApiRoutes } from './routes';
export const initSyntheticsServer = (
server: SyntheticsServerSetup,
@@ -19,6 +19,7 @@ export const initSyntheticsServer = (
plugins: SyntheticsPluginsSetupDependencies,
ruleDataClient: IRuleDataClient
) => {
+ const { router } = server;
syntheticsAppRestApiRoutes.forEach((route) => {
const { method, options, handler, validate, path } = syntheticsRouteWrapper(
createSyntheticsRouteWithAuth(route),
@@ -34,16 +35,103 @@ export const initSyntheticsServer = (
switch (method) {
case 'GET':
- server.router.get(routeDefinition, handler);
+ router.get(routeDefinition, handler);
break;
case 'POST':
- server.router.post(routeDefinition, handler);
+ router.post(routeDefinition, handler);
break;
case 'PUT':
- server.router.put(routeDefinition, handler);
+ router.put(routeDefinition, handler);
break;
case 'DELETE':
- server.router.delete(routeDefinition, handler);
+ router.delete(routeDefinition, handler);
+ break;
+ default:
+ throw new Error(`Handler for method ${method} is not defined`);
+ }
+ });
+
+ syntheticsAppPublicRestApiRoutes.forEach((route) => {
+ const { method, options, handler, validate, path, validation } = syntheticsRouteWrapper(
+ createSyntheticsRouteWithAuth(route),
+ server,
+ syntheticsMonitorClient
+ );
+
+ const routeDefinition = {
+ path,
+ validate,
+ options,
+ };
+
+ switch (method) {
+ case 'GET':
+ router.versioned
+ .get({
+ access: 'public',
+ path: routeDefinition.path,
+ options: {
+ tags: options?.tags,
+ },
+ })
+ .addVersion(
+ {
+ version: '2023-10-31',
+ validate: validation ?? false,
+ },
+ handler
+ );
+ break;
+ case 'PUT':
+ router.versioned
+ .put({
+ access: 'public',
+ path: routeDefinition.path,
+ options: {
+ tags: options?.tags,
+ },
+ })
+ .addVersion(
+ {
+ version: '2023-10-31',
+ validate: validation ?? false,
+ },
+ handler
+ );
+ break;
+ case 'POST':
+ router.versioned
+ .post({
+ access: 'public',
+ path: routeDefinition.path,
+ options: {
+ tags: options?.tags,
+ },
+ })
+ .addVersion(
+ {
+ version: '2023-10-31',
+ validate: validation ?? false,
+ },
+ handler
+ );
+ break;
+ case 'DELETE':
+ router.versioned
+ .delete({
+ access: 'public',
+ path: routeDefinition.path,
+ options: {
+ tags: options?.tags,
+ },
+ })
+ .addVersion(
+ {
+ version: '2023-10-31',
+ validate: validation ?? false,
+ },
+ handler
+ );
break;
default:
throw new Error(`Handler for method ${method} is not defined`);
diff --git a/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts b/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts
index f5d9256a13b4d63..4de02eb80b30c8c 100644
--- a/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts
+++ b/x-pack/test/api_integration/apis/synthetics/add_edit_params.ts
@@ -94,9 +94,9 @@ export default function ({ getService }: FtrProviderContext) {
assertHas(param, testParam);
await supertestAPI
- .put(SYNTHETICS_API_URLS.PARAMS)
+ .put(SYNTHETICS_API_URLS.PARAMS + '/' + param.id)
.set('kbn-xsrf', 'true')
- .send({ ...expectedUpdatedParam, id: param.id })
+ .send(expectedUpdatedParam)
.expect(200);
const updatedGetResponse = await supertestAPI
@@ -155,9 +155,9 @@ export default function ({ getService }: FtrProviderContext) {
assertHas(param, testParam);
await supertestAPI
- .put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
+ .put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}/${param.id}`)
.set('kbn-xsrf', 'true')
- .send({ ...expectedUpdatedParam, id: param.id })
+ .send(expectedUpdatedParam)
.expect(200);
const updatedGetResponse = await supertestAPI
@@ -204,9 +204,9 @@ export default function ({ getService }: FtrProviderContext) {
.expect(200);
await supertestAPI
- .put(`/s/${SPACE_ID_TWO}${SYNTHETICS_API_URLS.PARAMS}`)
+ .put(`/s/${SPACE_ID_TWO}${SYNTHETICS_API_URLS.PARAMS}/${param.id}}`)
.set('kbn-xsrf', 'true')
- .send({ ...updatedParam, id: param.id })
+ .send(updatedParam)
.expect(404);
const updatedGetResponse = await supertestAPI
@@ -251,9 +251,9 @@ export default function ({ getService }: FtrProviderContext) {
assertHas(param, testParam);
await supertestAPI
- .put(`/s/doesnotexist${SYNTHETICS_API_URLS.PARAMS}`)
+ .put(`/s/doesnotexist${SYNTHETICS_API_URLS.PARAMS}/${param.id}}`)
.set('kbn-xsrf', 'true')
- .send({ ...updatedParam, id: param.id })
+ .send(updatedParam)
.expect(404);
});
diff --git a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts
index 742463495595113..d6ae4e8f78228b2 100644
--- a/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts
+++ b/x-pack/test/api_integration/apis/synthetics/sync_global_params.ts
@@ -22,8 +22,6 @@ import { PrivateLocationTestService } from './services/private_location_test_ser
import { comparePolicies, getTestSyntheticsPolicy } from './sample_data/test_policy';
export default function ({ getService }: FtrProviderContext) {
- // FLAKY: https://github.com/elastic/kibana/issues/162594
- // Failing: See https://github.com/elastic/kibana/issues/162594
describe('SyncGlobalParams', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertest');
@@ -278,8 +276,8 @@ export default function ({ getService }: FtrProviderContext) {
const deleteResponse = await supertestAPI
.delete(SYNTHETICS_API_URLS.PARAMS)
- .query({ ids: JSON.stringify(ids) })
.set('kbn-xsrf', 'true')
+ .send({ ids })
.expect(200);
expect(deleteResponse.body).to.have.length(2);