Skip to content

Commit

Permalink
[ML] Adds reset action to transforms management.
Browse files Browse the repository at this point in the history
  • Loading branch information
walterra committed Jan 25, 2022
1 parent 0305c6e commit 07d9e7a
Show file tree
Hide file tree
Showing 15 changed files with 554 additions and 2 deletions.
32 changes: 32 additions & 0 deletions x-pack/plugins/transform/common/api_schemas/reset_transforms.ts
Original file line number Diff line number Diff line change
@@ -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<typeof resetTransformsRequestSchema>;

export interface ResetTransformStatus {
transformReset: ResponseStatus;
}

export interface ResetTransformsResponseSchema {
[key: string]: ResetTransformStatus;
}
10 changes: 10 additions & 0 deletions x-pack/plugins/transform/common/api_schemas/type_guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
};
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/transform/public/app/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
15 changes: 15 additions & 0 deletions x-pack/plugins/transform/public/app/hooks/use_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -157,6 +161,17 @@ export const useApi = () => {
return e;
}
},
async resetTransforms(
reqBody: ResetTransformsRequestSchema
): Promise<ResetTransformsResponseSchema | HttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}reset_transforms`, {
body: JSON.stringify(reqBody),
});
} catch (e) {
return e;
}
},
async startTransforms(
reqBody: StartTransformsRequestSchema
): Promise<StartTransformsResponseSchema | HttpFetchError> {
Expand Down
112 changes: 112 additions & 0 deletions x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx
Original file line number Diff line number Diff line change
@@ -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<ResetTransformStatus, 'destinationIndex'>;

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(
<ToastNotificationText
previewTextLength={50}
overlays={overlays}
theme={theme}
text={getErrorMessage(results)}
/>,
{ theme$: theme.theme$ }
),
});
return;
}

const isBulk = Object.keys(results).length > 1;
const successCount: Record<SuccessCountField, number> = {
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(
<ToastNotificationText
previewTextLength={50}
overlays={overlays}
theme={theme}
text={error}
/>,
{ 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);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const initialCapabilities: Capabilities = {
canStartStopTransform: false,
canCreateTransformAlerts: false,
canUseTransformAlerts: false,
canResetTransform: false,
};

const initialValue: Authorization = {
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Capabilities {
canStartStopTransform: boolean;
canCreateTransformAlerts: boolean;
canUseTransformAlerts: boolean;
canResetTransform: boolean;
}

export type Privilege = [string, string];
Expand Down Expand Up @@ -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.',
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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<ResetAction> = ({
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 = (
<>
<EuiFlexGroup direction="column" gutterSize="none">
{hasManagedTransforms ? (
<>
<ManagedTransformsWarningCallout
count={items.length}
action={i18n.translate(
'xpack.transform.transformList.resetManagedTransformDescription',
{ defaultMessage: 'resetting' }
)}
/>
<EuiSpacer />
</>
) : null}
</EuiFlexGroup>
</>
);

const resetModalContent = (
<>
<EuiFlexGroup direction="column" gutterSize="none">
{hasManagedTransforms ? (
<>
<ManagedTransformsWarningCallout
count={1}
action={i18n.translate(
'xpack.transform.transformList.resetManagedTransformDescription',
{ defaultMessage: 'deleting' }
)}
/>
<EuiSpacer />
</>
) : null}
</EuiFlexGroup>
</>
);

return (
<EuiConfirmModal
data-test-subj="transformResetModal"
title={isBulkAction === true ? bulkResetModalTitle : resetModalTitle}
onCancel={closeModal}
onConfirm={resetAndCloseModal}
cancelButtonText={i18n.translate('xpack.transform.transformList.resetModalCancelButton', {
defaultMessage: 'Cancel',
})}
confirmButtonText={i18n.translate('xpack.transform.transformList.resetModalResetButton', {
defaultMessage: 'Reset',
})}
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
buttonColor="danger"
>
{isBulkAction ? bulkResetModalContent : resetModalContent}
</EuiConfirmModal>
);
};
Loading

0 comments on commit 07d9e7a

Please sign in to comment.