diff --git a/x-pack/plugins/transform/common/api_schemas/reset_transforms.ts b/x-pack/plugins/transform/common/api_schemas/reset_transforms.ts new file mode 100644 index 0000000000000..951f5ad87716a --- /dev/null +++ b/x-pack/plugins/transform/common/api_schemas/reset_transforms.ts @@ -0,0 +1,32 @@ +/* + * 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 { transformStateSchema, ResponseStatus } from './common'; + +export const resetTransformsRequestSchema = schema.object({ + /** + * Reset Transforms + */ + transformsInfo: schema.arrayOf( + schema.object({ + id: schema.string(), + state: transformStateSchema, + }) + ), +}); + +export type ResetTransformsRequestSchema = TypeOf; + +export interface ResetTransformStatus { + transformReset: ResponseStatus; +} + +export interface ResetTransformsResponseSchema { + [key: string]: ResetTransformStatus; +} diff --git a/x-pack/plugins/transform/common/api_schemas/type_guards.ts b/x-pack/plugins/transform/common/api_schemas/type_guards.ts index 6c572a195b65f..3af08e57f7198 100644 --- a/x-pack/plugins/transform/common/api_schemas/type_guards.ts +++ b/x-pack/plugins/transform/common/api_schemas/type_guards.ts @@ -16,6 +16,7 @@ import { isPopulatedObject } from '../shared_imports'; import type { FieldHistogramsResponseSchema } from './field_histograms'; import type { GetTransformsAuditMessagesResponseSchema } from './audit_messages'; import type { DeleteTransformsResponseSchema } from './delete_transforms'; +import type { ResetTransformsResponseSchema } from './reset_transforms'; import type { StartTransformsResponseSchema } from './start_transforms'; import type { StopTransformsResponseSchema } from './stop_transforms'; import type { @@ -56,6 +57,15 @@ export const isDeleteTransformsResponseSchema = ( ); }; +export const isResetTransformsResponseSchema = ( + arg: unknown +): arg is ResetTransformsResponseSchema => { + return ( + isPopulatedObject(arg) && + Object.values(arg).every((d) => isPopulatedObject(d, ['transformReset'])) + ); +}; + export const isEsIndices = (arg: unknown): arg is EsIndex[] => { return Array.isArray(arg); }; diff --git a/x-pack/plugins/transform/public/app/hooks/index.ts b/x-pack/plugins/transform/public/app/hooks/index.ts index b050192fb9fb3..88333224f786b 100644 --- a/x-pack/plugins/transform/public/app/hooks/index.ts +++ b/x-pack/plugins/transform/public/app/hooks/index.ts @@ -8,6 +8,7 @@ export { useApi } from './use_api'; export { useGetTransforms } from './use_get_transforms'; export { useDeleteTransforms, useDeleteIndexAndTargetIndex } from './use_delete_transform'; +export { useResetTransforms } from './use_reset_transform'; export { useStartTransforms } from './use_start_transform'; export { useStopTransforms } from './use_stop_transform'; export { useRequest } from './use_request'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts index 21e37ca16c4de..64ea34c9470cf 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_api.ts @@ -22,6 +22,10 @@ import type { FieldHistogramsRequestSchema, FieldHistogramsResponseSchema, } from '../../../common/api_schemas/field_histograms'; +import type { + ResetTransformsRequestSchema, + ResetTransformsResponseSchema, +} from '../../../common/api_schemas/reset_transforms'; import type { StartTransformsRequestSchema, StartTransformsResponseSchema, @@ -157,6 +161,17 @@ export const useApi = () => { return e; } }, + async resetTransforms( + reqBody: ResetTransformsRequestSchema + ): Promise { + try { + return await http.post(`${API_BASE_PATH}reset_transforms`, { + body: JSON.stringify(reqBody), + }); + } catch (e) { + return e; + } + }, async startTransforms( reqBody: StartTransformsRequestSchema ): Promise { diff --git a/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx new file mode 100644 index 0000000000000..5c76276b34600 --- /dev/null +++ b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx @@ -0,0 +1,112 @@ +/* + * 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 { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; +import type { + ResetTransformStatus, + ResetTransformsRequestSchema, +} from '../../../common/api_schemas/reset_transforms'; +import { isResetTransformsResponseSchema } from '../../../common/api_schemas/type_guards'; +import { getErrorMessage } from '../../../common/utils/errors'; +import { useAppDependencies, useToastNotifications } from '../app_dependencies'; +import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$ } from '../common'; +import { ToastNotificationText } from '../components'; +import { useApi } from './use_api'; + +type SuccessCountField = keyof Omit; + +export const useResetTransforms = () => { + const { overlays, theme } = useAppDependencies(); + const toastNotifications = useToastNotifications(); + const api = useApi(); + + return async (reqBody: ResetTransformsRequestSchema) => { + const results = await api.resetTransforms(reqBody); + + if (!isResetTransformsResponseSchema(results)) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.transformList.resetTransformGenericErrorMessage', { + defaultMessage: 'An error occurred calling the API endpoint to reset transforms.', + }), + text: toMountPoint( + , + { theme$: theme.theme$ } + ), + }); + return; + } + + const isBulk = Object.keys(results).length > 1; + const successCount: Record = { + transformReset: 0, + }; + for (const transformId in results) { + // hasOwnProperty check to ensure only properties on object itself, and not its prototypes + if (results.hasOwnProperty(transformId)) { + const status = results[transformId]; + + // if we are only deleting one transform, show the success toast messages + if (!isBulk && status.transformReset) { + if (status.transformReset?.success) { + toastNotifications.addSuccess( + i18n.translate('xpack.transform.transformList.resetTransformSuccessMessage', { + defaultMessage: 'Request to reset transform {transformId} acknowledged.', + values: { transformId }, + }) + ); + } + } else { + (Object.keys(successCount) as SuccessCountField[]).forEach((key) => { + if (status[key]?.success) { + successCount[key] = successCount[key] + 1; + } + }); + } + if (status.transformReset?.error) { + const error = status.transformReset.error.reason; + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.transformList.resetTransformErrorMessage', { + defaultMessage: 'An error occurred resetting the transform {transformId}', + values: { transformId }, + }), + text: toMountPoint( + , + { theme$: theme.theme$ } + ), + }); + } + } + } + + // if we are deleting multiple transforms, combine the success messages + if (isBulk) { + if (successCount.transformReset > 0) { + toastNotifications.addSuccess( + i18n.translate('xpack.transform.transformList.bulkResetTransformSuccessMessage', { + defaultMessage: + 'Successfully reset {count} {count, plural, one {transform} other {transforms}}.', + values: { count: successCount.transformReset }, + }) + ); + } + } + + refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH); + }; +}; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx index cc6313bf058c6..cae840b86a833 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx @@ -28,6 +28,7 @@ const initialCapabilities: Capabilities = { canStartStopTransform: false, canCreateTransformAlerts: false, canUseTransformAlerts: false, + canResetTransform: false, }; const initialValue: Authorization = { @@ -77,6 +78,8 @@ export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) = 'cluster:admin/transform/delete', ]); + value.capabilities.canResetTransform = hasPrivilege(['cluster', 'cluster:admin/transform/reset']); + value.capabilities.canPreviewTransform = hasPrivilege([ 'cluster', 'cluster:admin/transform/preview', diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts index d430a4d059e5c..659d525643965 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts @@ -18,6 +18,7 @@ export interface Capabilities { canStartStopTransform: boolean; canCreateTransformAlerts: boolean; canUseTransformAlerts: boolean; + canResetTransform: boolean; } export type Privilege = [string, string]; @@ -91,6 +92,12 @@ export function createCapabilityFailureMessage( }); break; + case 'canResetTransform': + message = i18n.translate('xpack.transform.capability.noPermission.resetTransformTooltip', { + defaultMessage: 'You do not have permission to reset transforms.', + }); + break; + case 'noTransformNodes': message = i18n.translate('xpack.transform.capability.noPermission.noTransformNodesTooltip', { defaultMessage: 'There are no transform nodes available.', diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/index.ts new file mode 100644 index 0000000000000..62f4d42715579 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export { useResetAction } from './use_reset_action'; +export { ResetActionModal } from './reset_action_modal'; +export { isResetActionDisabled, ResetActionName } from './reset_action_name'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_modal.tsx new file mode 100644 index 0000000000000..efe5a0c0b09fa --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_modal.tsx @@ -0,0 +1,88 @@ +/* + * 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, { FC, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import { ResetAction } from './use_reset_action'; +import { isManagedTransform } from '../../../../common/managed_transforms_utils'; +import { ManagedTransformsWarningCallout } from '../managed_transforms_callout/managed_transforms_callout'; + +export const ResetActionModal: FC = ({ + closeModal, + resetAndCloseModal, + items, + shouldForceReset, +}) => { + const hasManagedTransforms = useMemo(() => items.some((t) => isManagedTransform(t)), [items]); + const isBulkAction = items.length > 1; + + const bulkResetModalTitle = i18n.translate('xpack.transform.transformList.bulkResetModalTitle', { + defaultMessage: 'Reset {count} {count, plural, one {transform} other {transforms}}?', + values: { count: items.length }, + }); + const resetModalTitle = i18n.translate('xpack.transform.transformList.resetModalTitle', { + defaultMessage: 'Reset {transformId}?', + values: { transformId: items[0] && items[0].config.id }, + }); + const bulkResetModalContent = ( + <> + + {hasManagedTransforms ? ( + <> + + + + ) : null} + + + ); + + const resetModalContent = ( + <> + + {hasManagedTransforms ? ( + <> + + + + ) : null} + + + ); + + return ( + + {isBulkAction ? bulkResetModalContent : resetModalContent} + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx new file mode 100644 index 0000000000000..8180ddaf26045 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/reset_action_name.tsx @@ -0,0 +1,70 @@ +/* + * 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, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip } from '@elastic/eui'; +import { TransformState, TRANSFORM_STATE } from '../../../../../../common/constants'; +import { createCapabilityFailureMessage } from '../../../../lib/authorization'; +import { TransformListRow } from '../../../../common'; + +export const resetActionNameText = i18n.translate( + 'xpack.transform.transformList.resetActionNameText', + { + defaultMessage: 'Reset', + } +); + +const transformCanNotBeReseted = (i: TransformListRow) => + !([TRANSFORM_STATE.STOPPED, TRANSFORM_STATE.FAILED] as TransformState[]).includes(i.stats.state); + +export const isResetActionDisabled = (items: TransformListRow[], forceDisable: boolean) => { + const disabled = items.some(transformCanNotBeReseted); + return forceDisable === true || disabled; +}; + +export interface ResetActionNameProps { + canResetTransform: boolean; + disabled: boolean; + isBulkAction: boolean; +} + +export const ResetActionName: FC = ({ + canResetTransform, + disabled, + isBulkAction, +}) => { + const bulkResetButtonDisabledText = i18n.translate( + 'xpack.transform.transformList.resetBulkActionDisabledToolTipContent', + { + defaultMessage: 'One or more selected transforms must be stopped in order to be reseted.', + } + ); + const resetButtonDisabledText = i18n.translate( + 'xpack.transform.transformList.resetActionDisabledToolTipContent', + { + defaultMessage: 'Stop the transform in order to reset it.', + } + ); + + if (disabled || !canResetTransform) { + let content; + if (disabled) { + content = isBulkAction ? bulkResetButtonDisabledText : resetButtonDisabledText; + } else { + content = createCapabilityFailureMessage('canResetTransform'); + } + + return ( + + <>{resetActionNameText} + + ); + } + + return <>{resetActionNameText}; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx new file mode 100644 index 0000000000000..70164bc22a63c --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_reset/use_reset_action.tsx @@ -0,0 +1,83 @@ +/* + * 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, { useContext, useMemo, useState } from 'react'; + +import { TRANSFORM_STATE } from '../../../../../../common/constants'; + +import { TransformListAction, TransformListRow } from '../../../../common'; +import { useResetTransforms } from '../../../../hooks'; +import { AuthorizationContext } from '../../../../lib/authorization'; + +import { resetActionNameText, isResetActionDisabled, ResetActionName } from './reset_action_name'; + +export type ResetAction = ReturnType; +export const useResetAction = (forceDisable: boolean) => { + const { canResetTransform } = useContext(AuthorizationContext).capabilities; + + const resetTransforms = useResetTransforms(); + + const [isModalVisible, setModalVisible] = useState(false); + const [items, setItems] = useState([]); + + const shouldForceReset = useMemo( + () => items.some((i: TransformListRow) => i.stats.state === TRANSFORM_STATE.FAILED), + [items] + ); + + const closeModal = () => setModalVisible(false); + + const resetAndCloseModal = () => { + setModalVisible(false); + + resetTransforms({ + transformsInfo: items.map((i) => ({ + id: i.config.id, + state: i.stats.state, + })), + }); + }; + + const openModal = (newItems: TransformListRow[]) => { + if (Array.isArray(newItems)) { + setItems(newItems); + setModalVisible(true); + } + }; + + const action: TransformListAction = useMemo( + () => ({ + name: (item: TransformListRow) => ( + + ), + enabled: (item: TransformListRow) => + !isResetActionDisabled([item], forceDisable) && canResetTransform, + description: resetActionNameText, + icon: 'refresh', + type: 'icon', + onClick: (item: TransformListRow) => openModal([item]), + 'data-test-subj': 'transformActionReset', + }), + [canResetTransform, forceDisable] + ); + + return { + action, + closeModal, + resetAndCloseModal, + isModalVisible, + items, + openModal, + shouldForceReset, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx index cdf0c14409fdd..4c8447feae1ee 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx @@ -41,6 +41,12 @@ import { DeleteActionName, DeleteActionModal, } from '../action_delete'; +import { + isResetActionDisabled, + useResetAction, + ResetActionName, + ResetActionModal, +} from '../action_reset'; import { useStartAction, StartActionName, StartActionModal } from '../action_start'; import { StopActionName, useStopAction } from '../action_stop'; @@ -92,6 +98,7 @@ export const TransformList: FC = ({ const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false); const bulkStartAction = useStartAction(false, transformNodes); const bulkDeleteAction = useDeleteAction(false); + const bulkResetAction = useResetAction(false); const bulkStopAction = useStopAction(false); const [searchError, setSearchError] = useState(undefined); @@ -196,6 +203,19 @@ export const TransformList: FC = ({ , +
+ { + bulkResetAction.openModal(transformSelection); + }} + > + + +
,
bulkDeleteAction.openModal(transformSelection)}> = ({ {/* Bulk Action Modals */} {bulkStartAction.isModalVisible && } {bulkDeleteAction.isModalVisible && } + {bulkResetAction.isModalVisible && } {bulkStopAction.isModalVisible && } {/* Single Action Modals */} diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx index 853c839a096fa..5d480003c7600 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx @@ -16,6 +16,7 @@ import { useDeleteAction, DeleteActionModal } from '../action_delete'; import { useDiscoverAction } from '../action_discover'; import { EditTransformFlyout } from '../edit_transform_flyout'; import { useEditAction } from '../action_edit'; +import { useResetAction, ResetActionModal } from '../action_reset'; import { useStartAction, StartActionModal } from '../action_start'; import { useStopAction } from '../action_stop'; import { useCreateAlertRuleAction } from '../action_create_alert'; @@ -35,6 +36,7 @@ export const useActions = ({ const deleteAction = useDeleteAction(forceDisable); const discoverAction = useDiscoverAction(forceDisable); const editAction = useEditAction(forceDisable, transformNodes); + const resetAction = useResetAction(forceDisable); const startAction = useStartAction(forceDisable, transformNodes); const stopAction = useStopAction(forceDisable); const createAlertRuleAction = useCreateAlertRuleAction(forceDisable); @@ -42,6 +44,7 @@ export const useActions = ({ return { modals: ( <> + {resetAction.isModalVisible && } {startAction.isModalVisible && } {stopAction.isModalVisible && } @@ -63,6 +66,7 @@ export const useActions = ({ editAction.action, cloneAction.action, deleteAction.action, + resetAction.action, ], }; }; diff --git a/x-pack/plugins/transform/server/routes/api/error_utils.ts b/x-pack/plugins/transform/server/routes/api/error_utils.ts index 7c4ac88576678..d9b892b7afcee 100644 --- a/x-pack/plugins/transform/server/routes/api/error_utils.ts +++ b/x-pack/plugins/transform/server/routes/api/error_utils.ts @@ -13,6 +13,7 @@ import { ResponseError, CustomHttpResponseOptions } from 'src/core/server'; import { CommonResponseStatusSchema, TransformIdsSchema } from '../../../common/api_schemas/common'; import { DeleteTransformsResponseSchema } from '../../../common/api_schemas/delete_transforms'; +import { ResetTransformsResponseSchema } from '../../../common/api_schemas/reset_transforms'; const REQUEST_TIMEOUT = 'RequestTimeout'; @@ -21,7 +22,10 @@ export function isRequestTimeout(error: any) { } interface Params { - results: CommonResponseStatusSchema | DeleteTransformsResponseSchema; + results: + | CommonResponseStatusSchema + | DeleteTransformsResponseSchema + | ResetTransformsResponseSchema; id: string; items: TransformIdsSchema; action: string; @@ -60,7 +64,10 @@ export function fillResultsWithTimeouts({ results, id, items, action }: Params) ], }; - const newResults: CommonResponseStatusSchema | DeleteTransformsResponseSchema = {}; + const newResults: + | CommonResponseStatusSchema + | DeleteTransformsResponseSchema + | ResetTransformsResponseSchema = {}; return items.reduce((accumResults, currentVal) => { if (results[currentVal.id] === undefined) { diff --git a/x-pack/plugins/transform/server/routes/api/transforms.ts b/x-pack/plugins/transform/server/routes/api/transforms.ts index deef8898fdc66..1f2cdb5b3b3df 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms.ts @@ -27,6 +27,11 @@ import { DeleteTransformsRequestSchema, DeleteTransformsResponseSchema, } from '../../../common/api_schemas/delete_transforms'; +import { + resetTransformsRequestSchema, + ResetTransformsRequestSchema, + ResetTransformsResponseSchema, +} from '../../../common/api_schemas/reset_transforms'; import { startTransformsRequestSchema, StartTransformsRequestSchema, @@ -316,6 +321,46 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { ) ); + /** + * @apiGroup Transforms + * + * @api {post} /api/transform/reset_transforms Post reset transforms + * @apiName ResetTransforms + * @apiDescription resets transforms + * + * @apiSchema (body) resetTransformsRequestSchema + */ + router.post( + { + path: addBasePath('reset_transforms'), + validate: { + body: resetTransformsRequestSchema, + }, + }, + license.guardApiRoute( + async (ctx, req, res) => { + try { + const body = await resetTransforms(req.body, ctx, res); + + if (body && body.status) { + if (body.status === 404) { + return res.notFound(); + } + if (body.status === 403) { + return res.forbidden(); + } + } + + return res.ok({ + body, + }); + } catch (e) { + return res.customError(wrapError(wrapEsError(e))); + } + } + ) + ); + /** * @apiGroup Transforms * @@ -547,6 +592,50 @@ async function deleteTransforms( return results; } +async function resetTransforms( + reqBody: ResetTransformsRequestSchema, + ctx: RequestHandlerContext, + response: KibanaResponseFactory +) { + const { transformsInfo } = reqBody; + + const results: ResetTransformsResponseSchema = {}; + + for (const transformInfo of transformsInfo) { + const transformReset: ResponseStatus = { success: false }; + const transformId = transformInfo.id; + + try { + try { + await ctx.core.elasticsearch.client.asCurrentUser.transform.resetTransform({ + transform_id: transformId, + }); + transformReset.success = true; + } catch (deleteTransformJobError) { + transformReset.error = deleteTransformJobError.meta.body.error; + if (deleteTransformJobError.statusCode === 403) { + return response.forbidden(); + } + } + + results[transformId] = { + transformReset, + }; + } catch (e) { + if (isRequestTimeout(e)) { + return fillResultsWithTimeouts({ + results, + id: transformInfo.id, + items: transformsInfo, + action: TRANSFORM_ACTIONS.DELETE, + }); + } + results[transformId] = { transformReset: { success: false, error: e.meta.body.error } }; + } + } + return results; +} + const previewTransformHandler: RequestHandler< undefined, undefined,