mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Synthetics] Refactor bulk delete monitor and params routes !! (#195420)
## Summary Refactor bulk delete monitor and params routes !! We need to remove usage for body from DELETE route. ### Params Params can be bulk delete now with POST request to `/params/_bulk_delete` endpoint ### Monitors Monitors can be bulk delete now with POST request to `/monitors/_bulk_delete` endpoint
This commit is contained in:
parent
12dc8c1bb8
commit
d25a2b442a
26 changed files with 403 additions and 197 deletions
|
@ -17,9 +17,6 @@ Deletes one or more monitors from the Synthetics app.
|
|||
You must have `all` privileges for the *Synthetics* feature in the *{observability}* section of the
|
||||
<<kibana-feature-privileges,{kib} feature privileges>>.
|
||||
|
||||
You must have `all` privileges for the *Synthetics* feature in the *{observability}* section of the
|
||||
<<kibana-feature-privileges,{kib} feature privileges>>.
|
||||
|
||||
|
||||
[[delete-monitor-api-path-params]]
|
||||
=== {api-path-parms-title}
|
||||
|
@ -27,7 +24,6 @@ You must have `all` privileges for the *Synthetics* feature in the *{observabili
|
|||
`config_id`::
|
||||
(Required, string) The ID of the monitor that you want to delete.
|
||||
|
||||
|
||||
Here is an example of a DELETE request to delete a monitor by ID:
|
||||
|
||||
[source,sh]
|
||||
|
@ -37,7 +33,7 @@ DELETE /api/synthetics/monitors/monitor1-id
|
|||
|
||||
==== Bulk Delete Monitors
|
||||
|
||||
You can delete multiple monitors by sending a list of config ids to a DELETE request to the `/api/synthetics/monitors` endpoint.
|
||||
You can delete multiple monitors by sending a list of config ids to a POST request to the `/api/synthetics/monitors/_bulk_delete` endpoint.
|
||||
|
||||
|
||||
[[monitors-delete-request-body]]
|
||||
|
@ -49,11 +45,11 @@ The request body should contain an array of monitors IDs that you want to delete
|
|||
(Required, array of strings) An array of monitor IDs to delete.
|
||||
|
||||
|
||||
Here is an example of a DELETE request to delete a list of monitors by ID:
|
||||
Here is an example of a POST request to delete a list of monitors by ID:
|
||||
|
||||
[source,sh]
|
||||
--------------------------------------------------
|
||||
DELETE /api/synthetics/monitors
|
||||
POST /api/synthetics/monitors/_bulk_delete
|
||||
{
|
||||
"ids": [
|
||||
"monitor1-id",
|
||||
|
|
|
@ -8,9 +8,9 @@ Deletes one or more parameters from the Synthetics app.
|
|||
|
||||
=== {api-request-title}
|
||||
|
||||
`DELETE <kibana host>:<port>/api/synthetics/params`
|
||||
`DELETE <kibana host>:<port>/api/synthetics/params/<param_id>`
|
||||
|
||||
`DELETE <kibana host>:<port>/s/<space_id>/api/synthetics/params`
|
||||
`DELETE <kibana host>:<port>/s/<space_id>/api/synthetics/params/<param_id>`
|
||||
|
||||
=== {api-prereq-title}
|
||||
|
||||
|
@ -20,26 +20,19 @@ You must have `all` privileges for the *Synthetics* feature in the *{observabili
|
|||
You must have `all` privileges for the *Synthetics* feature in the *{observability}* section of the
|
||||
<<kibana-feature-privileges,{kib} feature privileges>>.
|
||||
|
||||
[[parameters-delete-request-body]]
|
||||
==== Request Body
|
||||
[[parameters-delete-path-param]]
|
||||
==== Path Parameters
|
||||
|
||||
The request body should contain an array of parameter IDs that you want to delete.
|
||||
|
||||
`ids`::
|
||||
(Required, array of strings) An array of parameter IDs to delete.
|
||||
`param_id`::
|
||||
(Required, string) An id of parameter to delete.
|
||||
|
||||
|
||||
Here is an example of a DELETE request to delete a list of parameters by ID:
|
||||
Here is an example of a DELETE request to delete a parameter by its ID:
|
||||
|
||||
[source,sh]
|
||||
--------------------------------------------------
|
||||
DELETE /api/synthetics/params
|
||||
{
|
||||
"ids": [
|
||||
"param1-id",
|
||||
"param2-id"
|
||||
]
|
||||
}
|
||||
DELETE /api/synthetics/params/param_id1
|
||||
--------------------------------------------------
|
||||
|
||||
[[parameters-delete-response-example]]
|
||||
|
@ -58,10 +51,21 @@ Here's an example response for deleting multiple parameters:
|
|||
{
|
||||
"id": "param1-id",
|
||||
"deleted": true
|
||||
},
|
||||
{
|
||||
"id": "param2-id",
|
||||
"deleted": true
|
||||
}
|
||||
]
|
||||
--------------------------------------------------
|
||||
--------------------------------------------------
|
||||
|
||||
==== Bulk delete parameters
|
||||
To delete multiple parameters, you can send a POST request to `/api/synthetics/params/_bulk_delete` with an array of parameter IDs to delete via body.
|
||||
|
||||
Here is an example of a POST request to delete multiple parameters:
|
||||
|
||||
[source,sh]
|
||||
--------------------------------------------------
|
||||
POST /api/synthetics/params/_bulk_delete
|
||||
{
|
||||
"ids": ["param1-id", "param2-id"]
|
||||
}
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ export const SyntheticsParamsReadonlyCodec = t.intersection([
|
|||
}),
|
||||
]);
|
||||
|
||||
export const SyntheticsParamsReadonlyCodecList = t.array(SyntheticsParamsReadonlyCodec);
|
||||
|
||||
export type SyntheticsParamsReadonly = t.TypeOf<typeof SyntheticsParamsReadonlyCodec>;
|
||||
|
||||
export const SyntheticsParamsCodec = t.intersection([
|
||||
|
|
|
@ -5,16 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { EuiConfirmModal } from '@elastic/eui';
|
||||
import { FETCH_STATUS, useFetcher } from '@kbn/observability-shared-plugin/public';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { getGlobalParamAction, deleteGlobalParams } from '../../../state/global_params';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
getGlobalParamAction,
|
||||
deleteGlobalParamsAction,
|
||||
selectGlobalParamState,
|
||||
} from '../../../state/global_params';
|
||||
import { syncGlobalParamsAction } from '../../../state/settings';
|
||||
import { kibanaService } from '../../../../../utils/kibana_service';
|
||||
import { NO_LABEL, YES_LABEL } from '../../monitors_page/management/monitor_list_table/labels';
|
||||
import { ListParamItem } from './params_list';
|
||||
|
||||
|
@ -25,19 +26,8 @@ export const DeleteParam = ({
|
|||
items: ListParamItem[];
|
||||
setIsDeleteModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const [isDeleting, setIsDeleting] = useState<boolean>(false);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
setIsDeleting(true);
|
||||
};
|
||||
|
||||
const { status } = useFetcher(() => {
|
||||
if (isDeleting) {
|
||||
return deleteGlobalParams(items.map(({ id }) => id));
|
||||
}
|
||||
}, [items, isDeleting]);
|
||||
const { isDeleting, listOfParams } = useSelector(selectGlobalParamState);
|
||||
|
||||
const name = items
|
||||
.map(({ key }) => key)
|
||||
|
@ -45,51 +35,12 @@ export const DeleteParam = ({
|
|||
.slice(0, 50);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeleting) {
|
||||
return;
|
||||
}
|
||||
const { coreStart, toasts } = kibanaService;
|
||||
|
||||
if (status === FETCH_STATUS.FAILURE) {
|
||||
toasts.addDanger(
|
||||
{
|
||||
title: toMountPoint(
|
||||
<p data-test-subj="uptimeDeleteParamFailure">
|
||||
{' '}
|
||||
{i18n.translate('xpack.synthetics.paramManagement.paramDeleteFailuresMessage.name', {
|
||||
defaultMessage: 'Param {name} failed to delete.',
|
||||
values: { name },
|
||||
})}
|
||||
</p>,
|
||||
coreStart
|
||||
),
|
||||
},
|
||||
{ toastLifeTimeMs: 3000 }
|
||||
);
|
||||
} else if (status === FETCH_STATUS.SUCCESS) {
|
||||
toasts.addSuccess(
|
||||
{
|
||||
title: toMountPoint(
|
||||
<p data-test-subj="uptimeDeleteParamSuccess">
|
||||
{i18n.translate('xpack.synthetics.paramManagement.paramDeleteSuccessMessage.name', {
|
||||
defaultMessage: 'Param {name} deleted successfully.',
|
||||
values: { name },
|
||||
})}
|
||||
</p>,
|
||||
coreStart
|
||||
),
|
||||
},
|
||||
{ toastLifeTimeMs: 3000 }
|
||||
);
|
||||
dispatch(syncGlobalParamsAction.get());
|
||||
}
|
||||
if (status === FETCH_STATUS.SUCCESS || status === FETCH_STATUS.FAILURE) {
|
||||
setIsDeleting(false);
|
||||
if (!isDeleting && (listOfParams ?? []).length === 0) {
|
||||
setIsDeleteModalVisible(false);
|
||||
dispatch(getGlobalParamAction.get());
|
||||
dispatch(syncGlobalParamsAction.get());
|
||||
}
|
||||
}, [setIsDeleting, isDeleting, status, setIsDeleteModalVisible, name, dispatch]);
|
||||
}, [isDeleting, setIsDeleteModalVisible, name, dispatch, listOfParams]);
|
||||
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
|
@ -98,7 +49,9 @@ export const DeleteParam = ({
|
|||
values: { name },
|
||||
})}
|
||||
onCancel={() => setIsDeleteModalVisible(false)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onConfirm={() => {
|
||||
dispatch(deleteGlobalParamsAction.get(items.map(({ id }) => id)));
|
||||
}}
|
||||
cancelButtonText={NO_LABEL}
|
||||
confirmButtonText={YES_LABEL}
|
||||
buttonColor="danger"
|
||||
|
|
|
@ -83,7 +83,11 @@ export const ParamsList = () => {
|
|||
render: (val: string[]) => {
|
||||
const tags = val ?? [];
|
||||
if (tags.length === 0) {
|
||||
return <EuiText>--</EuiText>;
|
||||
return (
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.synthetics.columns.TextLabel', { defaultMessage: '--' })}
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" wrap>
|
||||
|
@ -105,7 +109,11 @@ export const ParamsList = () => {
|
|||
render: (val: string[]) => {
|
||||
const namespaces = val ?? [];
|
||||
if (namespaces.length === 0) {
|
||||
return <EuiText>--</EuiText>;
|
||||
return (
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.synthetics.columns.TextLabel', { defaultMessage: '--' })}
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" wrap>
|
||||
|
@ -184,6 +192,7 @@ export const ParamsList = () => {
|
|||
isEditingItem={isEditingItem}
|
||||
setIsEditingItem={setIsEditingItem}
|
||||
items={items}
|
||||
key="add-param-flyout"
|
||||
/>,
|
||||
];
|
||||
};
|
||||
|
|
|
@ -23,3 +23,8 @@ export const editGlobalParamAction = createAsyncAction<
|
|||
},
|
||||
SyntheticsParams
|
||||
>('EDIT GLOBAL PARAM');
|
||||
|
||||
export const deleteGlobalParamsAction = createAsyncAction<
|
||||
string[],
|
||||
Array<{ id: string; deleted: boolean }>
|
||||
>('DELETE GLOBAL PARAMS');
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
SyntheticsParams,
|
||||
SyntheticsParamsCodec,
|
||||
SyntheticsParamsReadonlyCodec,
|
||||
SyntheticsParamsReadonlyCodecList,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { apiService } from '../../../../utils/api_service/api_service';
|
||||
|
||||
|
@ -20,14 +21,14 @@ export const getGlobalParams = async (): Promise<SyntheticsParams[]> => {
|
|||
return apiService.get<SyntheticsParams[]>(
|
||||
SYNTHETICS_API_URLS.PARAMS,
|
||||
{ version: INITIAL_REST_VERSION },
|
||||
SyntheticsParamsReadonlyCodec
|
||||
SyntheticsParamsReadonlyCodecList
|
||||
);
|
||||
};
|
||||
|
||||
export const addGlobalParam = async (
|
||||
paramRequest: SyntheticsParamRequest
|
||||
): Promise<SyntheticsParams> =>
|
||||
apiService.post(SYNTHETICS_API_URLS.PARAMS, paramRequest, SyntheticsParamsCodec, {
|
||||
apiService.post(SYNTHETICS_API_URLS.PARAMS, paramRequest, SyntheticsParamsReadonlyCodec, {
|
||||
version: INITIAL_REST_VERSION,
|
||||
});
|
||||
|
||||
|
@ -53,11 +54,13 @@ export const editGlobalParam = async ({
|
|||
);
|
||||
};
|
||||
|
||||
export const deleteGlobalParams = async (ids: string[]): Promise<DeleteParamsResponse[]> =>
|
||||
apiService.delete(
|
||||
SYNTHETICS_API_URLS.PARAMS,
|
||||
{ version: INITIAL_REST_VERSION },
|
||||
export const deleteGlobalParams = async (ids: string[]): Promise<DeleteParamsResponse[]> => {
|
||||
return await apiService.post(
|
||||
SYNTHETICS_API_URLS.PARAMS + '/_bulk_delete',
|
||||
{
|
||||
ids,
|
||||
}
|
||||
},
|
||||
null,
|
||||
{ version: INITIAL_REST_VERSION }
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,8 +8,13 @@
|
|||
import { takeLeading } from 'redux-saga/effects';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { fetchEffectFactory } from '../utils/fetch_effect';
|
||||
import { addGlobalParam, editGlobalParam, getGlobalParams } from './api';
|
||||
import { addNewGlobalParamAction, editGlobalParamAction, getGlobalParamAction } from './actions';
|
||||
import { addGlobalParam, deleteGlobalParams, editGlobalParam, getGlobalParams } from './api';
|
||||
import {
|
||||
addNewGlobalParamAction,
|
||||
deleteGlobalParamsAction,
|
||||
editGlobalParamAction,
|
||||
getGlobalParamAction,
|
||||
} from './actions';
|
||||
|
||||
export function* getGlobalParamEffect() {
|
||||
yield takeLeading(
|
||||
|
@ -69,3 +74,26 @@ const editSuccessMessage = i18n.translate('xpack.synthetics.settings.editParams.
|
|||
const editFailureMessage = i18n.translate('xpack.synthetics.settings.editParams.fail', {
|
||||
defaultMessage: 'Failed to edit global parameter.',
|
||||
});
|
||||
|
||||
// deleteGlobalParams
|
||||
|
||||
export function* deleteGlobalParamsEffect() {
|
||||
yield takeLeading(
|
||||
deleteGlobalParamsAction.get,
|
||||
fetchEffectFactory(
|
||||
deleteGlobalParams,
|
||||
deleteGlobalParamsAction.success,
|
||||
deleteGlobalParamsAction.fail,
|
||||
deleteSuccessMessage,
|
||||
deleteFailureMessage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const deleteSuccessMessage = i18n.translate('xpack.synthetics.settings.deleteParams.success', {
|
||||
defaultMessage: 'Successfully deleted global parameters.',
|
||||
});
|
||||
|
||||
const deleteFailureMessage = i18n.translate('xpack.synthetics.settings.deleteParams.fail', {
|
||||
defaultMessage: 'Failed to delete global parameters.',
|
||||
});
|
||||
|
|
|
@ -8,7 +8,12 @@
|
|||
import { createReducer } from '@reduxjs/toolkit';
|
||||
import { SyntheticsParams } from '../../../../../common/runtime_types';
|
||||
import { IHttpSerializedFetchError } from '..';
|
||||
import { addNewGlobalParamAction, editGlobalParamAction, getGlobalParamAction } from './actions';
|
||||
import {
|
||||
addNewGlobalParamAction,
|
||||
deleteGlobalParamsAction,
|
||||
editGlobalParamAction,
|
||||
getGlobalParamAction,
|
||||
} from './actions';
|
||||
|
||||
export interface GlobalParamsState {
|
||||
isLoading?: boolean;
|
||||
|
@ -16,6 +21,7 @@ export interface GlobalParamsState {
|
|||
addError: IHttpSerializedFetchError | null;
|
||||
editError: IHttpSerializedFetchError | null;
|
||||
isSaving?: boolean;
|
||||
isDeleting?: boolean;
|
||||
savedData?: SyntheticsParams;
|
||||
}
|
||||
|
||||
|
@ -23,6 +29,7 @@ const initialState: GlobalParamsState = {
|
|||
isLoading: false,
|
||||
addError: null,
|
||||
isSaving: false,
|
||||
isDeleting: false,
|
||||
editError: null,
|
||||
listOfParams: [],
|
||||
};
|
||||
|
@ -62,6 +69,16 @@ export const globalParamsReducer = createReducer(initialState, (builder) => {
|
|||
.addCase(editGlobalParamAction.fail, (state, action) => {
|
||||
state.isSaving = false;
|
||||
state.editError = action.payload;
|
||||
})
|
||||
.addCase(deleteGlobalParamsAction.get, (state) => {
|
||||
state.isDeleting = true;
|
||||
})
|
||||
.addCase(deleteGlobalParamsAction.success, (state) => {
|
||||
state.isDeleting = false;
|
||||
state.listOfParams = [];
|
||||
})
|
||||
.addCase(deleteGlobalParamsAction.fail, (state) => {
|
||||
state.isDeleting = false;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -60,12 +60,13 @@ export const fetchDeleteMonitor = async ({
|
|||
}): Promise<void> => {
|
||||
const baseUrl = SYNTHETICS_API_URLS.SYNTHETICS_MONITORS;
|
||||
|
||||
return await apiService.delete(
|
||||
baseUrl,
|
||||
{ version: INITIAL_REST_VERSION, spaceId },
|
||||
return await apiService.post(
|
||||
baseUrl + '/_bulk_delete',
|
||||
{
|
||||
ids: configIds,
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
{ version: INITIAL_REST_VERSION, spaceId }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
import { all, fork } from 'redux-saga/effects';
|
||||
import { getCertsListEffect } from './certs';
|
||||
import { addGlobalParamEffect, editGlobalParamEffect, getGlobalParamEffect } from './global_params';
|
||||
import {
|
||||
addGlobalParamEffect,
|
||||
deleteGlobalParamsEffect,
|
||||
editGlobalParamEffect,
|
||||
getGlobalParamEffect,
|
||||
} from './global_params';
|
||||
import { fetchManualTestRunsEffect } from './manual_test_runs/effects';
|
||||
import {
|
||||
enableDefaultAlertingEffect,
|
||||
|
@ -66,6 +71,7 @@ export const rootEffect = function* root(): Generator {
|
|||
fork(fetchManualTestRunsEffect),
|
||||
fork(addGlobalParamEffect),
|
||||
fork(editGlobalParamEffect),
|
||||
fork(deleteGlobalParamsEffect),
|
||||
fork(getGlobalParamEffect),
|
||||
fork(getCertsListEffect),
|
||||
fork(getDefaultAlertingEffect),
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { deleteSyntheticsParamsBulkRoute } from './settings/params/delete_params_bulk';
|
||||
import { deleteSyntheticsMonitorBulkRoute } from './monitor_cruds/bulk_cruds/delete_monitor_bulk';
|
||||
import {
|
||||
createGetDynamicSettingsRoute,
|
||||
createPostDynamicSettingsRoute,
|
||||
|
@ -113,4 +115,6 @@ export const syntheticsAppPublicRestApiRoutes: SyntheticsRestApiRouteFactory[] =
|
|||
addSyntheticsMonitorRoute,
|
||||
editSyntheticsMonitorRoute,
|
||||
deleteSyntheticsMonitorRoute,
|
||||
deleteSyntheticsMonitorBulkRoute,
|
||||
deleteSyntheticsParamsBulkRoute,
|
||||
];
|
||||
|
|
|
@ -10,7 +10,6 @@ import { SavedObjectsBulkResponse } from '@kbn/core-saved-objects-api-server';
|
|||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
import { SavedObjectError } from '@kbn/core-saved-objects-common';
|
||||
import { deleteMonitorBulk } from './delete_monitor_bulk';
|
||||
import { SyntheticsServerSetup } from '../../../types';
|
||||
import { RouteContext } from '../../types';
|
||||
import { formatTelemetryEvent, sendTelemetryEvents } from '../../telemetry/monitor_upgrade_sender';
|
||||
|
@ -190,9 +189,10 @@ export const deleteMonitorIfCreated = async ({
|
|||
newMonitorId
|
||||
);
|
||||
if (encryptedMonitor) {
|
||||
await deleteMonitorBulk({
|
||||
const deleteMonitorAPI = new DeleteMonitorAPI(routeContext);
|
||||
|
||||
await deleteMonitorAPI.deleteMonitorBulk({
|
||||
monitors: [encryptedMonitor],
|
||||
routeContext,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -4,63 +4,40 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { SavedObject } from '@kbn/core-saved-objects-server';
|
||||
import {
|
||||
formatTelemetryDeleteEvent,
|
||||
sendTelemetryEvents,
|
||||
} from '../../telemetry/monitor_upgrade_sender';
|
||||
import {
|
||||
ConfigKey,
|
||||
MonitorFields,
|
||||
SyntheticsMonitor,
|
||||
EncryptedSyntheticsMonitorAttributes,
|
||||
SyntheticsMonitorWithId,
|
||||
} from '../../../../common/runtime_types';
|
||||
import { syntheticsMonitorType } from '../../../../common/types/saved_objects';
|
||||
import { RouteContext } from '../../types';
|
||||
|
||||
export const deleteMonitorBulk = async ({
|
||||
monitors,
|
||||
routeContext,
|
||||
}: {
|
||||
monitors: Array<SavedObject<SyntheticsMonitor | EncryptedSyntheticsMonitorAttributes>>;
|
||||
routeContext: RouteContext;
|
||||
}) => {
|
||||
const { savedObjectsClient, server, spaceId, syntheticsMonitorClient } = routeContext;
|
||||
const { logger, telemetry, stackVersion } = server;
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { DeleteMonitorAPI } from '../services/delete_monitor_api';
|
||||
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
|
||||
import { SyntheticsRestApiRouteFactory } from '../../types';
|
||||
|
||||
try {
|
||||
const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors(
|
||||
monitors.map((normalizedMonitor) => ({
|
||||
...normalizedMonitor.attributes,
|
||||
id: normalizedMonitor.attributes[ConfigKey.MONITOR_QUERY_ID],
|
||||
})) as SyntheticsMonitorWithId[],
|
||||
savedObjectsClient,
|
||||
spaceId
|
||||
);
|
||||
export const deleteSyntheticsMonitorBulkRoute: SyntheticsRestApiRouteFactory<
|
||||
Array<{ id: string; deleted: boolean }>,
|
||||
Record<string, string>,
|
||||
Record<string, string>,
|
||||
{ ids: string[] }
|
||||
> = () => ({
|
||||
method: 'POST',
|
||||
path: SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/_bulk_delete',
|
||||
validate: {},
|
||||
validation: {
|
||||
request: {
|
||||
body: schema.object({
|
||||
ids: schema.arrayOf(schema.string(), {
|
||||
minSize: 1,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async (routeContext): Promise<any> => {
|
||||
const { request } = routeContext;
|
||||
|
||||
const deletePromises = savedObjectsClient.bulkDelete(
|
||||
monitors.map((monitor) => ({ type: syntheticsMonitorType, id: monitor.id }))
|
||||
);
|
||||
const { ids: idsToDelete } = request.body || {};
|
||||
const deleteMonitorAPI = new DeleteMonitorAPI(routeContext);
|
||||
|
||||
const [errors, result] = await Promise.all([deleteSyncPromise, deletePromises]);
|
||||
|
||||
monitors.forEach((monitor) => {
|
||||
sendTelemetryEvents(
|
||||
logger,
|
||||
telemetry,
|
||||
formatTelemetryDeleteEvent(
|
||||
monitor,
|
||||
stackVersion,
|
||||
new Date().toISOString(),
|
||||
Boolean((monitor.attributes as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
errors
|
||||
)
|
||||
);
|
||||
const { errors, result } = await deleteMonitorAPI.execute({
|
||||
monitorIds: idsToDelete,
|
||||
});
|
||||
|
||||
return { errors, result };
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
return { result, errors };
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { DeleteMonitorAPI } from './services/delete_monitor_api';
|
||||
import { SyntheticsRestApiRouteFactory } from '../types';
|
||||
|
@ -41,30 +42,39 @@ export const deleteSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory<
|
|||
|
||||
if (ids && queryId) {
|
||||
return response.badRequest({
|
||||
body: { message: 'id must be provided either via param or body.' },
|
||||
body: {
|
||||
message: i18n.translate('xpack.synthetics.deleteMonitor.errorMultipleIdsProvided', {
|
||||
defaultMessage: 'id must be provided either via param or body.',
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const idsToDelete = [...(ids ?? []), ...(queryId ? [queryId] : [])];
|
||||
if (idsToDelete.length === 0) {
|
||||
return response.badRequest({
|
||||
body: { message: 'id must be provided via param or body.' },
|
||||
body: {
|
||||
message: i18n.translate('xpack.synthetics.deleteMonitor.errorMultipleIdsProvided', {
|
||||
defaultMessage: 'id must be provided either via param or body.',
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const deleteMonitorAPI = new DeleteMonitorAPI(routeContext);
|
||||
try {
|
||||
const { errors } = await deleteMonitorAPI.execute({
|
||||
monitorIds: idsToDelete,
|
||||
});
|
||||
const { errors } = await deleteMonitorAPI.execute({
|
||||
monitorIds: idsToDelete,
|
||||
});
|
||||
|
||||
if (errors && errors.length > 0) {
|
||||
return response.ok({
|
||||
body: { message: 'error pushing monitor to the service', attributes: { errors } },
|
||||
});
|
||||
}
|
||||
} catch (getErr) {
|
||||
throw getErr;
|
||||
if (errors && errors.length > 0) {
|
||||
return response.ok({
|
||||
body: {
|
||||
message: i18n.translate('xpack.synthetics.deleteMonitor.errorPushingMonitorToService', {
|
||||
defaultMessage: 'Error pushing monitor to the service',
|
||||
}),
|
||||
attributes: { errors },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return deleteMonitorAPI.result;
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DeleteMonitorAPI } from './services/delete_monitor_api';
|
||||
import { SyntheticsRestApiRouteFactory } from '../types';
|
||||
import { syntheticsMonitorType } from '../../../common/types/saved_objects';
|
||||
import { ConfigKey } from '../../../common/runtime_types';
|
||||
import { SYNTHETICS_API_URLS } from '../../../common/constants';
|
||||
import { getMonitors, getSavedObjectKqlFilter } from '../common';
|
||||
import { deleteMonitorBulk } from './bulk_cruds/delete_monitor_bulk';
|
||||
import { validateSpaceId } from './services/validate_space_id';
|
||||
|
||||
export const deleteSyntheticsMonitorProjectRoute: SyntheticsRestApiRouteFactory = () => ({
|
||||
|
@ -58,9 +58,10 @@ export const deleteSyntheticsMonitorProjectRoute: SyntheticsRestApiRouteFactory
|
|||
{ fields: [] }
|
||||
);
|
||||
|
||||
await deleteMonitorBulk({
|
||||
const deleteMonitorAPI = new DeleteMonitorAPI(routeContext);
|
||||
|
||||
await deleteMonitorAPI.deleteMonitorBulk({
|
||||
monitors,
|
||||
routeContext,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -7,16 +7,22 @@
|
|||
|
||||
import pMap from 'p-map';
|
||||
import { SavedObject, SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server';
|
||||
import { deleteMonitorBulk } from '../bulk_cruds/delete_monitor_bulk';
|
||||
import { validatePermissions } from '../edit_monitor';
|
||||
import {
|
||||
ConfigKey,
|
||||
EncryptedSyntheticsMonitorAttributes,
|
||||
MonitorFields,
|
||||
SyntheticsMonitor,
|
||||
SyntheticsMonitorWithId,
|
||||
SyntheticsMonitorWithSecretsAttributes,
|
||||
} from '../../../../common/runtime_types';
|
||||
import { syntheticsMonitorType } from '../../../../common/types/saved_objects';
|
||||
import { normalizeSecrets } from '../../../synthetics_service/utils';
|
||||
import { sendErrorTelemetryEvents } from '../../telemetry/monitor_upgrade_sender';
|
||||
import {
|
||||
formatTelemetryDeleteEvent,
|
||||
sendErrorTelemetryEvents,
|
||||
sendTelemetryEvents,
|
||||
} from '../../telemetry/monitor_upgrade_sender';
|
||||
import { RouteContext } from '../../types';
|
||||
|
||||
export class DeleteMonitorAPI {
|
||||
|
@ -100,9 +106,8 @@ export class DeleteMonitorAPI {
|
|||
}
|
||||
|
||||
try {
|
||||
const { errors, result } = await deleteMonitorBulk({
|
||||
const { errors, result } = await this.deleteMonitorBulk({
|
||||
monitors,
|
||||
routeContext: this.routeContext,
|
||||
});
|
||||
|
||||
result.statuses?.forEach((res) => {
|
||||
|
@ -112,11 +117,55 @@ export class DeleteMonitorAPI {
|
|||
});
|
||||
});
|
||||
|
||||
return { errors };
|
||||
return { errors, result: this.result };
|
||||
} catch (e) {
|
||||
server.logger.error(`Unable to delete Synthetics monitor with error ${e.message}`);
|
||||
server.logger.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMonitorBulk({
|
||||
monitors,
|
||||
}: {
|
||||
monitors: Array<SavedObject<SyntheticsMonitor | EncryptedSyntheticsMonitorAttributes>>;
|
||||
}) {
|
||||
const { savedObjectsClient, server, spaceId, syntheticsMonitorClient } = this.routeContext;
|
||||
const { logger, telemetry, stackVersion } = server;
|
||||
|
||||
try {
|
||||
const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors(
|
||||
monitors.map((normalizedMonitor) => ({
|
||||
...normalizedMonitor.attributes,
|
||||
id: normalizedMonitor.attributes[ConfigKey.MONITOR_QUERY_ID],
|
||||
})) as SyntheticsMonitorWithId[],
|
||||
savedObjectsClient,
|
||||
spaceId
|
||||
);
|
||||
|
||||
const deletePromises = savedObjectsClient.bulkDelete(
|
||||
monitors.map((monitor) => ({ type: syntheticsMonitorType, id: monitor.id }))
|
||||
);
|
||||
|
||||
const [errors, result] = await Promise.all([deleteSyncPromise, deletePromises]);
|
||||
|
||||
monitors.forEach((monitor) => {
|
||||
sendTelemetryEvents(
|
||||
logger,
|
||||
telemetry,
|
||||
formatTelemetryDeleteEvent(
|
||||
monitor,
|
||||
stackVersion,
|
||||
new Date().toISOString(),
|
||||
Boolean((monitor.attributes as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
errors
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return { errors, result };
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SyntheticsRestApiRouteFactory } from '../../types';
|
||||
import { syntheticsParamType } from '../../../../common/types/saved_objects';
|
||||
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
|
||||
|
@ -13,25 +14,51 @@ import { DeleteParamsResponse } from '../../../../common/runtime_types';
|
|||
|
||||
export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory<
|
||||
DeleteParamsResponse[],
|
||||
unknown,
|
||||
{ id?: string },
|
||||
unknown,
|
||||
{ ids: string[] }
|
||||
> = () => ({
|
||||
method: 'DELETE',
|
||||
path: SYNTHETICS_API_URLS.PARAMS,
|
||||
path: SYNTHETICS_API_URLS.PARAMS + '/{id?}',
|
||||
validate: {},
|
||||
validation: {
|
||||
request: {
|
||||
body: schema.object({
|
||||
ids: schema.arrayOf(schema.string()),
|
||||
body: schema.nullable(
|
||||
schema.object({
|
||||
ids: schema.arrayOf(schema.string(), {
|
||||
minSize: 1,
|
||||
}),
|
||||
})
|
||||
),
|
||||
params: schema.object({
|
||||
id: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async ({ savedObjectsClient, request }) => {
|
||||
const { ids } = request.body;
|
||||
handler: async ({ savedObjectsClient, request, response }) => {
|
||||
const { ids } = request.body ?? {};
|
||||
const { id: paramId } = request.params ?? {};
|
||||
|
||||
if (ids && paramId) {
|
||||
return response.badRequest({
|
||||
body: i18n.translate('xpack.synthetics.deleteParam.errorMultipleIdsProvided', {
|
||||
defaultMessage: `Both param id and body parameters cannot be provided`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const idsToDelete = ids ?? [paramId];
|
||||
|
||||
if (idsToDelete.length === 0) {
|
||||
return response.badRequest({
|
||||
body: i18n.translate('xpack.synthetics.deleteParam.errorNoIdsProvided', {
|
||||
defaultMessage: `No param ids provided`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const result = await savedObjectsClient.bulkDelete(
|
||||
ids.map((id) => ({ type: syntheticsParamType, id })),
|
||||
idsToDelete.map((id) => ({ type: syntheticsParamType, id })),
|
||||
{ force: true }
|
||||
);
|
||||
return result.statuses.map(({ id, success }) => ({ id, deleted: success }));
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 deleteSyntheticsParamsBulkRoute: SyntheticsRestApiRouteFactory<
|
||||
DeleteParamsResponse[],
|
||||
unknown,
|
||||
unknown,
|
||||
{ ids: string[] }
|
||||
> = () => ({
|
||||
method: 'POST',
|
||||
path: SYNTHETICS_API_URLS.PARAMS + '/_bulk_delete',
|
||||
validate: {},
|
||||
validation: {
|
||||
request: {
|
||||
body: schema.object({
|
||||
ids: schema.arrayOf(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
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 }));
|
||||
},
|
||||
});
|
|
@ -44331,8 +44331,6 @@
|
|||
"xpack.synthetics.paramForm.namespaces": "Espaces de noms",
|
||||
"xpack.synthetics.paramForm.sharedAcrossSpacesLabel": "Partager entre les espaces",
|
||||
"xpack.synthetics.paramManagement.deleteParamNameLabel": "Supprimer le paramètre \"{name}\" ?",
|
||||
"xpack.synthetics.paramManagement.paramDeleteFailuresMessage.name": "Impossible de supprimer le paramètre {name}.",
|
||||
"xpack.synthetics.paramManagement.paramDeleteSuccessMessage.name": "Paramètre {name} supprimé avec succès.",
|
||||
"xpack.synthetics.params.description": "Définissez les variables et paramètres que vous pouvez utiliser dans la configuration du navigateur et des moniteurs légers, tels que des informations d'identification ou des URL. {learnMore}",
|
||||
"xpack.synthetics.params.unprivileged.unprivilegedDescription": "Vous devez disposer de privilèges supplémentaires pour voir les paramètres d'utilisation et de conservation des données des applications Synthetics. {docsLink}",
|
||||
"xpack.synthetics.pingList.collapseRow": "Réduire",
|
||||
|
|
|
@ -44069,8 +44069,6 @@
|
|||
"xpack.synthetics.paramForm.namespaces": "名前空間",
|
||||
"xpack.synthetics.paramForm.sharedAcrossSpacesLabel": "複数のスペース間で共有",
|
||||
"xpack.synthetics.paramManagement.deleteParamNameLabel": "\"{name}\"パラメーターを削除しますか?",
|
||||
"xpack.synthetics.paramManagement.paramDeleteFailuresMessage.name": "パラメーター{name}の削除に失敗しました。",
|
||||
"xpack.synthetics.paramManagement.paramDeleteSuccessMessage.name": "パラメーター\"{name}\"が正常に削除されました。",
|
||||
"xpack.synthetics.params.description": "ブラウザーや軽量モニターの設定に使用できる変数やパラメーター(認証情報やURLなど)を定義します。{learnMore}",
|
||||
"xpack.synthetics.params.unprivileged.unprivilegedDescription": "Syntheticsアプリデータの使用状況と保持設定を表示する追加の権限が必要です。{docsLink}",
|
||||
"xpack.synthetics.pingList.collapseRow": "縮小",
|
||||
|
|
|
@ -44119,8 +44119,6 @@
|
|||
"xpack.synthetics.paramForm.namespaces": "命名空间",
|
||||
"xpack.synthetics.paramForm.sharedAcrossSpacesLabel": "跨工作区共享",
|
||||
"xpack.synthetics.paramManagement.deleteParamNameLabel": "删除“{name}”参数?",
|
||||
"xpack.synthetics.paramManagement.paramDeleteFailuresMessage.name": "无法删除参数 {name}。",
|
||||
"xpack.synthetics.paramManagement.paramDeleteSuccessMessage.name": "已成功删除参数 {name}。",
|
||||
"xpack.synthetics.params.description": "定义可在浏览器和轻量级监测的配置中使用的变量和参数,如凭据或 URL。{learnMore}",
|
||||
"xpack.synthetics.params.unprivileged.unprivilegedDescription": "您需要其他权限才能查看 Synthetics 应用数据使用情况和保留设置。{docsLink}",
|
||||
"xpack.synthetics.pingList.collapseRow": "折叠",
|
||||
|
|
|
@ -353,5 +353,45 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(param.key).to.not.empty();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle bulk deleting params', async () => {
|
||||
await kServer.savedObjects.clean({ types: [syntheticsParamType] });
|
||||
|
||||
const params = [
|
||||
{ key: 'param1', value: 'value1' },
|
||||
{ key: 'param2', value: 'value2' },
|
||||
{ key: 'param3', value: 'value3' },
|
||||
];
|
||||
|
||||
for (const param of params) {
|
||||
await supertestAPI
|
||||
.post(SYNTHETICS_API_URLS.PARAMS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(param)
|
||||
.expect(200);
|
||||
}
|
||||
|
||||
const getResponse = await supertestAPI
|
||||
.get(SYNTHETICS_API_URLS.PARAMS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
expect(getResponse.body.length).to.eql(3);
|
||||
|
||||
const ids = getResponse.body.map((param: any) => param.id);
|
||||
|
||||
await supertestAPI
|
||||
.post(SYNTHETICS_API_URLS.PARAMS + '/_bulk_delete')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ids })
|
||||
.expect(200);
|
||||
|
||||
const getResponseAfterDelete = await supertestAPI
|
||||
.get(SYNTHETICS_API_URLS.PARAMS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
expect(getResponseAfterDelete.body.length).to.eql(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -121,6 +121,33 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await supertest.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId).expect(404);
|
||||
});
|
||||
|
||||
it('deletes multiple monitors by bulk delete', async () => {
|
||||
const { id: monitorId } = await saveMonitor(httpMonitorJson as MonitorFields);
|
||||
const { id: monitorId2 } = await saveMonitor({
|
||||
...httpMonitorJson,
|
||||
name: 'another -2',
|
||||
} as MonitorFields);
|
||||
|
||||
const deleteResponse = await monitorTestService.deleteMonitorBulk(
|
||||
[monitorId2, monitorId],
|
||||
200
|
||||
);
|
||||
|
||||
expect(
|
||||
deleteResponse.body.result.sort((a: { id: string }, b: { id: string }) =>
|
||||
a.id > b.id ? 1 : -1
|
||||
)
|
||||
).eql(
|
||||
[
|
||||
{ id: monitorId2, deleted: true },
|
||||
{ id: monitorId, deleted: true },
|
||||
].sort((a, b) => (a.id > b.id ? 1 : -1))
|
||||
);
|
||||
|
||||
// Hit get endpoint and expect 404 as well
|
||||
await supertest.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId).expect(404);
|
||||
});
|
||||
|
||||
it('returns 404 if monitor id is not found', async () => {
|
||||
const invalidMonitorId = 'invalid-id';
|
||||
const expected404Message = `Monitor id ${invalidMonitorId} not found!`;
|
||||
|
|
|
@ -230,4 +230,17 @@ export class SyntheticsMonitorTestService {
|
|||
expect(deleteResponse.status).to.eql(statusCode);
|
||||
return deleteResponse;
|
||||
}
|
||||
|
||||
async deleteMonitorBulk(monitorIds: string[], statusCode = 200, spaceId?: string) {
|
||||
const deleteResponse = await this.supertest
|
||||
.post(
|
||||
spaceId
|
||||
? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/_bulk_delete`
|
||||
: SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/_bulk_delete'
|
||||
)
|
||||
.send({ ids: monitorIds })
|
||||
.set('kbn-xsrf', 'true');
|
||||
expect(deleteResponse.status).to.eql(statusCode);
|
||||
return deleteResponse;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -287,8 +287,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const deleteResponse = await supertestAPI
|
||||
.delete(SYNTHETICS_API_URLS.PARAMS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ids })
|
||||
.expect(200);
|
||||
.send({ ids });
|
||||
|
||||
expect(deleteResponse.status).eql(200);
|
||||
|
||||
expect(deleteResponse.body).to.have.length(2);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue