[ML] Transform: Enable force delete if one of the transforms failed (#69472)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
1158be9264
commit
22d09a3bbd
|
@ -43,6 +43,7 @@ export interface DeleteTransformEndpointRequest {
|
|||
transformsInfo: TransformEndpointRequest[];
|
||||
deleteDestIndex?: boolean;
|
||||
deleteDestIndexPattern?: boolean;
|
||||
forceDelete?: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteTransformStatus {
|
||||
|
|
|
@ -47,10 +47,16 @@ export const useApi = () => {
|
|||
deleteTransforms(
|
||||
transformsInfo: TransformEndpointRequest[],
|
||||
deleteDestIndex: boolean | undefined,
|
||||
deleteDestIndexPattern: boolean | undefined
|
||||
deleteDestIndexPattern: boolean | undefined,
|
||||
forceDelete: boolean
|
||||
): Promise<DeleteTransformEndpointResult> {
|
||||
return http.post(`${API_BASE_PATH}delete_transforms`, {
|
||||
body: JSON.stringify({ transformsInfo, deleteDestIndex, deleteDestIndexPattern }),
|
||||
body: JSON.stringify({
|
||||
transformsInfo,
|
||||
deleteDestIndex,
|
||||
deleteDestIndexPattern,
|
||||
forceDelete,
|
||||
}),
|
||||
});
|
||||
},
|
||||
getTransformsPreview(obj: PreviewRequestBody): Promise<GetTransformsResponse> {
|
||||
|
|
|
@ -8,13 +8,13 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import {
|
||||
TransformEndpointRequest,
|
||||
DeleteTransformEndpointResult,
|
||||
DeleteTransformStatus,
|
||||
TransformEndpointRequest,
|
||||
} from '../../../common';
|
||||
import { getErrorMessage, extractErrorMessage } from '../../shared_imports';
|
||||
import { extractErrorMessage, getErrorMessage } from '../../shared_imports';
|
||||
import { useAppDependencies, useToastNotifications } from '../app_dependencies';
|
||||
import { TransformListRow, refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common';
|
||||
import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$, TransformListRow } from '../common';
|
||||
import { ToastNotificationText } from '../components';
|
||||
import { useApi } from './use_api';
|
||||
import { indexService } from '../services/es_index_service';
|
||||
|
@ -27,13 +27,13 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => {
|
|||
const [deleteIndexPattern, setDeleteIndexPattern] = useState<boolean>(true);
|
||||
const [userCanDeleteIndex, setUserCanDeleteIndex] = useState<boolean>(false);
|
||||
const [indexPatternExists, setIndexPatternExists] = useState<boolean>(false);
|
||||
|
||||
const toggleDeleteIndex = useCallback(() => setDeleteDestIndex(!deleteDestIndex), [
|
||||
deleteDestIndex,
|
||||
]);
|
||||
const toggleDeleteIndexPattern = useCallback(() => setDeleteIndexPattern(!deleteIndexPattern), [
|
||||
deleteIndexPattern,
|
||||
]);
|
||||
|
||||
const checkIndexPatternExists = useCallback(
|
||||
async (indexName: string) => {
|
||||
try {
|
||||
|
@ -79,6 +79,7 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => {
|
|||
useEffect(() => {
|
||||
checkUserIndexPermission();
|
||||
|
||||
// if user only deleting one transform
|
||||
if (items.length === 1) {
|
||||
const config = items[0].config;
|
||||
const destinationIndex = Array.isArray(config.dest.index)
|
||||
|
@ -110,7 +111,8 @@ export const useDeleteTransforms = () => {
|
|||
return async (
|
||||
transforms: TransformListRow[],
|
||||
shouldDeleteDestIndex: boolean,
|
||||
shouldDeleteDestIndexPattern: boolean
|
||||
shouldDeleteDestIndexPattern: boolean,
|
||||
shouldForceDelete = false
|
||||
) => {
|
||||
const transformsInfo: TransformEndpointRequest[] = transforms.map((tf) => ({
|
||||
id: tf.config.id,
|
||||
|
@ -121,7 +123,8 @@ export const useDeleteTransforms = () => {
|
|||
const results: DeleteTransformEndpointResult = await api.deleteTransforms(
|
||||
transformsInfo,
|
||||
shouldDeleteDestIndex,
|
||||
shouldDeleteDestIndexPattern
|
||||
shouldDeleteDestIndexPattern,
|
||||
shouldForceDelete
|
||||
);
|
||||
const isBulk = Object.keys(results).length > 1;
|
||||
const successCount: Record<SuccessCountField, number> = {
|
||||
|
|
|
@ -4,25 +4,25 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, FC, useContext, useState } from 'react';
|
||||
import React, { FC, Fragment, useContext, useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EUI_MODAL_CONFIRM_BUTTON,
|
||||
EuiButtonEmpty,
|
||||
EuiConfirmModal,
|
||||
EuiOverlayMask,
|
||||
EuiToolTip,
|
||||
EUI_MODAL_CONFIRM_BUTTON,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSwitch,
|
||||
EuiOverlayMask,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { TRANSFORM_STATE } from '../../../../../../common';
|
||||
import { useDeleteTransforms, useDeleteIndexAndTargetIndex } from '../../../../hooks';
|
||||
import { useDeleteIndexAndTargetIndex, useDeleteTransforms } from '../../../../hooks';
|
||||
import {
|
||||
createCapabilityFailureMessage,
|
||||
AuthorizationContext,
|
||||
createCapabilityFailureMessage,
|
||||
} from '../../../../lib/authorization';
|
||||
import { TransformListRow } from '../../../../common';
|
||||
|
||||
|
@ -31,11 +31,17 @@ interface DeleteActionProps {
|
|||
forceDisable?: boolean;
|
||||
}
|
||||
|
||||
const transformCanNotBeDeleted = (i: TransformListRow) =>
|
||||
![TRANSFORM_STATE.STOPPED, TRANSFORM_STATE.FAILED].includes(i.stats.state);
|
||||
|
||||
export const DeleteAction: FC<DeleteActionProps> = ({ items, forceDisable }) => {
|
||||
const isBulkAction = items.length > 1;
|
||||
|
||||
const disabled = items.some((i: TransformListRow) => i.stats.state !== TRANSFORM_STATE.STOPPED);
|
||||
|
||||
const disabled = items.some(transformCanNotBeDeleted);
|
||||
const shouldForceDelete = useMemo(
|
||||
() => items.some((i: TransformListRow) => i.stats.state === TRANSFORM_STATE.FAILED),
|
||||
[items]
|
||||
);
|
||||
const { canDeleteTransform } = useContext(AuthorizationContext).capabilities;
|
||||
const deleteTransforms = useDeleteTransforms();
|
||||
const {
|
||||
|
@ -56,7 +62,12 @@ export const DeleteAction: FC<DeleteActionProps> = ({ items, forceDisable }) =>
|
|||
const shouldDeleteDestIndex = userCanDeleteIndex && deleteDestIndex;
|
||||
const shouldDeleteDestIndexPattern =
|
||||
userCanDeleteIndex && indexPatternExists && deleteIndexPattern;
|
||||
deleteTransforms(items, shouldDeleteDestIndex, shouldDeleteDestIndexPattern);
|
||||
// if we are deleting multiple transforms, then force delete all if at least one item has failed
|
||||
// else, force delete only when the item user picks has failed
|
||||
const forceDelete = isBulkAction
|
||||
? shouldForceDelete
|
||||
: items[0] && items[0] && items[0].stats.state === TRANSFORM_STATE.FAILED;
|
||||
deleteTransforms(items, shouldDeleteDestIndex, shouldDeleteDestIndexPattern, forceDelete);
|
||||
};
|
||||
const openModal = () => setModalVisible(true);
|
||||
|
||||
|
@ -89,11 +100,19 @@ export const DeleteAction: FC<DeleteActionProps> = ({ items, forceDisable }) =>
|
|||
const bulkDeleteModalContent = (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.transform.transformList.bulkDeleteModalBody"
|
||||
defaultMessage="Are you sure you want to delete {count, plural, one {this} other {these}} {count} {count, plural, one {transform} other {transforms}}?"
|
||||
values={{ count: items.length }}
|
||||
/>
|
||||
{shouldForceDelete ? (
|
||||
<FormattedMessage
|
||||
id="xpack.transform.transformList.bulkForceDeleteModalBody"
|
||||
defaultMessage="Are you sure you want to force delete {count, plural, one {this} other {these}} {count} {count, plural, one {transform} other {transforms}}? The {count, plural, one {transform} other {transforms}} will be deleted regardless of {count, plural, one {its} other {their}} current state."
|
||||
values={{ count: items.length }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.transform.transformList.bulkDeleteModalBody"
|
||||
defaultMessage="Are you sure you want to delete {count, plural, one {this} other {these}} {count} {count, plural, one {transform} other {transforms}}?"
|
||||
values={{ count: items.length }}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
|
@ -134,10 +153,17 @@ export const DeleteAction: FC<DeleteActionProps> = ({ items, forceDisable }) =>
|
|||
const deleteModalContent = (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.transform.transformList.deleteModalBody"
|
||||
defaultMessage="Are you sure you want to delete this transform?"
|
||||
/>
|
||||
{items[0] && items[0] && items[0].stats.state === TRANSFORM_STATE.FAILED ? (
|
||||
<FormattedMessage
|
||||
id="xpack.transform.transformList.forceDeleteModalBody"
|
||||
defaultMessage="Are you sure you want to force delete this transform? The transform will be deleted regardless of its current state."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.transform.transformList.deleteModalBody"
|
||||
defaultMessage="Are you sure you want to delete this transform?"
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
|
|
|
@ -83,11 +83,14 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
|
|||
transform.deleteTransform = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_transform/<%=transformId%>',
|
||||
fmt: '/_transform/<%=transformId%>?&force=<%=force%>',
|
||||
req: {
|
||||
transformId: {
|
||||
type: 'string',
|
||||
},
|
||||
force: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -27,4 +27,5 @@ export const deleteTransformSchema = schema.object({
|
|||
),
|
||||
deleteDestIndex: schema.maybe(schema.boolean()),
|
||||
deleteDestIndexPattern: schema.maybe(schema.boolean()),
|
||||
forceDelete: schema.maybe(schema.boolean()),
|
||||
});
|
||||
|
|
|
@ -190,6 +190,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) {
|
|||
transformsInfo,
|
||||
deleteDestIndex,
|
||||
deleteDestIndexPattern,
|
||||
forceDelete,
|
||||
} = req.body as DeleteTransformEndpointRequest;
|
||||
|
||||
try {
|
||||
|
@ -197,6 +198,7 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) {
|
|||
transformsInfo,
|
||||
deleteDestIndex,
|
||||
deleteDestIndexPattern,
|
||||
forceDelete,
|
||||
ctx,
|
||||
license,
|
||||
res
|
||||
|
@ -295,39 +297,28 @@ async function deleteTransforms(
|
|||
transformsInfo: TransformEndpointRequest[],
|
||||
deleteDestIndex: boolean | undefined,
|
||||
deleteDestIndexPattern: boolean | undefined,
|
||||
shouldForceDelete: boolean = false,
|
||||
ctx: RequestHandlerContext,
|
||||
license: RouteDependencies['license'],
|
||||
response: KibanaResponseFactory
|
||||
) {
|
||||
const tempResults: TransformEndpointResult = {};
|
||||
const results: Record<string, DeleteTransformStatus> = {};
|
||||
|
||||
for (const transformInfo of transformsInfo) {
|
||||
let destinationIndex: string | undefined;
|
||||
|
||||
const transformDeleted: ResultData = { success: false };
|
||||
const destIndexDeleted: ResultData = { success: false };
|
||||
const destIndexPatternDeleted: ResultData = {
|
||||
success: false,
|
||||
};
|
||||
const transformId = transformInfo.id;
|
||||
// force delete only if the transform has failed
|
||||
let needToForceDelete = false;
|
||||
|
||||
try {
|
||||
if (transformInfo.state === TRANSFORM_STATE.FAILED) {
|
||||
try {
|
||||
await ctx.transform!.dataClient.callAsCurrentUser('transform.stopTransform', {
|
||||
transformId,
|
||||
force: true,
|
||||
waitForCompletion: true,
|
||||
} as StopOptions);
|
||||
} catch (e) {
|
||||
if (isRequestTimeout(e)) {
|
||||
return fillResultsWithTimeouts({
|
||||
results: tempResults,
|
||||
id: transformId,
|
||||
items: transformsInfo,
|
||||
action: TRANSFORM_ACTIONS.DELETE,
|
||||
});
|
||||
}
|
||||
}
|
||||
needToForceDelete = true;
|
||||
}
|
||||
// Grab destination index info to delete
|
||||
try {
|
||||
|
@ -383,6 +374,7 @@ async function deleteTransforms(
|
|||
try {
|
||||
await ctx.transform!.dataClient.callAsCurrentUser('transform.deleteTransform', {
|
||||
transformId,
|
||||
force: shouldForceDelete && needToForceDelete,
|
||||
});
|
||||
transformDeleted.success = true;
|
||||
} catch (deleteTransformJobError) {
|
||||
|
|
Loading…
Reference in a new issue