Skip to content

Commit

Permalink
[7.x] [ML] Transform: Enable force delete if one of the transforms fa…
Browse files Browse the repository at this point in the history
…iled (#69472) (#69764)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
qn895 and elasticmachine committed Jun 24, 2020
1 parent 6ef3178 commit 89ba7ff
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 45 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/transform/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface DeleteTransformEndpointRequest {
transformsInfo: TransformEndpointRequest[];
deleteDestIndex?: boolean;
deleteDestIndexPattern?: boolean;
forceDelete?: boolean;
}

export interface DeleteTransformStatus {
Expand Down
10 changes: 8 additions & 2 deletions x-pack/plugins/transform/public/app/hooks/use_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 {
Expand All @@ -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);

Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
},
],
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/transform/server/routes/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export const deleteTransformSchema = schema.object({
),
deleteDestIndex: schema.maybe(schema.boolean()),
deleteDestIndexPattern: schema.maybe(schema.boolean()),
forceDelete: schema.maybe(schema.boolean()),
});
26 changes: 9 additions & 17 deletions x-pack/plugins/transform/server/routes/api/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,15 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) {
transformsInfo,
deleteDestIndex,
deleteDestIndexPattern,
forceDelete,
} = req.body as DeleteTransformEndpointRequest;

try {
const body = await deleteTransforms(
transformsInfo,
deleteDestIndex,
deleteDestIndexPattern,
forceDelete,
ctx,
license,
res
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -383,6 +374,7 @@ async function deleteTransforms(
try {
await ctx.transform!.dataClient.callAsCurrentUser('transform.deleteTransform', {
transformId,
force: shouldForceDelete && needToForceDelete,
});
transformDeleted.success = true;
} catch (deleteTransformJobError) {
Expand Down

0 comments on commit 89ba7ff

Please sign in to comment.