Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Transforms: Adds _schedule_now action to transform list. #153545

Merged
merged 13 commits into from
Mar 26, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';

import { transformIdsSchema, CommonResponseStatusSchema } from './common';

export const scheduleNowTransformsRequestSchema = transformIdsSchema;
export type ScheduleNowTransformsRequestSchema = TypeOf<typeof scheduleNowTransformsRequestSchema>;
export type ScheduleNowTransformsResponseSchema = CommonResponseStatusSchema;
7 changes: 7 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 @@ -21,6 +21,7 @@ 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 { ScheduleNowTransformsResponseSchema } from './schedule_now_transforms';
import type {
GetTransformNodesResponseSchema,
GetTransformsResponseSchema,
Expand Down Expand Up @@ -144,3 +145,9 @@ export const isStopTransformsResponseSchema = (
): arg is StopTransformsResponseSchema => {
return isGenericSuccessResponseSchema(arg);
};

export const isScheduleNowTransformsResponseSchema = (
arg: unknown
): arg is ScheduleNowTransformsResponseSchema => {
return isGenericSuccessResponseSchema(arg);
};
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 @@ -9,6 +9,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 { useScheduleNowTransforms } from './use_schedule_now_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 @@ -34,6 +34,10 @@ import type {
StopTransformsRequestSchema,
StopTransformsResponseSchema,
} from '../../../common/api_schemas/stop_transforms';
import type {
ScheduleNowTransformsRequestSchema,
ScheduleNowTransformsResponseSchema,
} from '../../../common/api_schemas/schedule_now_transforms';
import type {
GetTransformNodesResponseSchema,
GetTransformsResponseSchema,
Expand Down Expand Up @@ -195,6 +199,17 @@ export const useApi = () => {
return e;
}
},
async scheduleNowTransforms(
transformsInfo: ScheduleNowTransformsRequestSchema
): Promise<ScheduleNowTransformsResponseSchema | IHttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}schedule_now_transforms`, {
body: JSON.stringify(transformsInfo),
});
} catch (e) {
return e;
}
},
async getTransformAuditMessages(
transformId: TransformId,
sortField: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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 '@kbn/kibana-react-plugin/public';

import type { ScheduleNowTransformsRequestSchema } from '../../../common/api_schemas/schedule_now_transforms';
import { isScheduleNowTransformsResponseSchema } from '../../../common/api_schemas/type_guards';

import { getErrorMessage } from '../../../common/utils/errors';

import { useAppDependencies, useToastNotifications } from '../app_dependencies';
import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common';
import { ToastNotificationText } from '../components';

import { useApi } from './use_api';

export const useScheduleNowTransforms = () => {
const { overlays, theme } = useAppDependencies();
const toastNotifications = useToastNotifications();
const api = useApi();

return async (transformsInfo: ScheduleNowTransformsRequestSchema) => {
const results = await api.scheduleNowTransforms(transformsInfo);

if (!isScheduleNowTransformsResponseSchema(results)) {
toastNotifications.addDanger({
title: i18n.translate(
'xpack.transform.stepCreateForm.scheduleNowTransformResponseSchemaErrorMessage',
{
defaultMessage: 'An error occurred calling the schedule now transforms request.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe An error occurred calling the request to schedule the transform. ?

Copy link
Contributor

@szabosteve szabosteve Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defaultMessage: 'An error occurred calling the schedule now transforms request.',
defaultMessage: 'An error occurred calling the request to schedule the transform to process data instantly.',

}
),
text: toMountPoint(
<ToastNotificationText
overlays={overlays}
theme={theme}
text={getErrorMessage(results)}
/>,
{ theme$: theme.theme$ }
),
});
return;
}

for (const transformId in results) {
// hasOwnProperty check to ensure only properties on object itself, and not its prototypes
if (results.hasOwnProperty(transformId)) {
const result = results[transformId];
if (result.success === true) {
toastNotifications.addSuccess(
i18n.translate('xpack.transform.transformList.scheduleNowTransformSuccessMessage', {
defaultMessage: 'Request to schedule now transform {transformId} acknowledged.',
values: { transformId },
})
);
} else {
toastNotifications.addError(new Error(JSON.stringify(result.error!.caused_by, null, 2)), {
title: i18n.translate(
'xpack.transform.transformList.scheduleNowTransformErrorMessage',
{
defaultMessage: 'An error occurred to schedule the transform {transformId} now',
qn895 marked this conversation as resolved.
Show resolved Hide resolved
values: { transformId },
}
),
toastMessage: result.error!.reason,
});
}
}
}

refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const initialCapabilities: Capabilities = {
canDeleteTransform: false,
canPreviewTransform: false,
canCreateTransform: false,
canScheduleNowTransform: false,
canStartStopTransform: false,
canCreateTransformAlerts: false,
canUseTransformAlerts: false,
Expand Down Expand Up @@ -94,6 +95,11 @@ export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) =

value.capabilities.canUseTransformAlerts = value.capabilities.canGetTransform;

value.capabilities.canScheduleNowTransform = hasPrivilege([
'cluster',
'cluster:admin/transform/schedule_now',
]);

return (
<AuthorizationContext.Provider value={{ ...value }}>{children}</AuthorizationContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface Capabilities {
canDeleteTransform: boolean;
canPreviewTransform: boolean;
canCreateTransform: boolean;
canScheduleNowTransform: boolean;
canStartStopTransform: boolean;
canCreateTransformAlerts: boolean;
canUseTransformAlerts: boolean;
Expand Down Expand Up @@ -78,6 +79,14 @@ export function createCapabilityFailureMessage(
}
);
break;
case 'canScheduleNowTransform':
message = i18n.translate(
'xpack.transform.capability.noPermission.scheduleNowTransformTooltip',
{
defaultMessage: 'You do not have permission to schedule transforms now.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need the word now here. You do not have permission to schedule transforms.

Copy link
Contributor

@szabosteve szabosteve Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defaultMessage: 'You do not have permission to schedule transforms now.',
defaultMessage: 'You do not have permission to schedule transforms to process data instantly.',

Copy link
Contributor

@szabosteve szabosteve Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defaultMessage: 'You do not have permission to schedule transforms now.',
defaultMessage: 'You do not have permission to schedule transforms to process data instantly.',

}
);
break;
case 'canStartStopTransform':
message = i18n.translate(
'xpack.transform.capability.noPermission.startOrStopTransformTooltip',
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 { useScheduleNowAction } from './use_schedule_now_action';
export { ScheduleNowActionModal } from './schedule_now_action_modal';
export { isScheduleNowActionDisabled, ScheduleNowActionName } from './schedule_now_action_name';
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui';
import { ScheduleNowAction } from './use_schedule_now_action';

export const ScheduleNowActionModal: FC<ScheduleNowAction> = ({
closeModal,
items,
scheduleNowAndCloseModal,
}) => {
const isBulkAction = items.length > 1;

const bulkScheduleNowModalTitle = i18n.translate(
'xpack.transform.transformList.bulkScheduleNowModalTitle',
{
defaultMessage: 'Schedule {count} {count, plural, one {transform} other {transforms}} now?',
values: { count: items && items.length },
}
);
const scheduleNowModalTitle = i18n.translate(
'xpack.transform.transformList.scheduleNowModalTitle',
{
defaultMessage: 'Schedule {transformId} now?',
values: { transformId: items[0] && items[0].config.id },
}
);

return (
<EuiConfirmModal
data-test-subj="transformScheduleNowModal"
title={isBulkAction === true ? bulkScheduleNowModalTitle : scheduleNowModalTitle}
onCancel={closeModal}
onConfirm={scheduleNowAndCloseModal}
cancelButtonText={i18n.translate(
'xpack.transform.transformList.scheduleNowModalCancelButton',
{
defaultMessage: 'Cancel',
}
)}
confirmButtonText={i18n.translate(
'xpack.transform.transformList.scheduleNowModalScheduleNowButton',
{
defaultMessage: 'Schedule now',
}
)}
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
buttonColor="primary"
>
<p>
{i18n.translate('xpack.transform.transformList.scheduleNowModalBody', {
defaultMessage:
'A transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.',
})}
</p>
</EuiConfirmModal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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, useContext } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiToolTip } from '@elastic/eui';

import {
createCapabilityFailureMessage,
AuthorizationContext,
} from '../../../../lib/authorization';
import { TransformListRow, isCompletedBatchTransform } from '../../../../common';

export const scheduleNowActionNameText = i18n.translate(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a tooltip to this button to provide clarification as to what this action does as @qn895 suggested?

Something like Instantly run the transform to process data without waiting for the configured interval between checks for changes in the source indices. ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a tooltip in a92b1ae.

'xpack.transform.transformList.scheduleNowActionNameText',
{
defaultMessage: 'Schedule now',
}
);

export const isScheduleNowActionDisabled = (
items: TransformListRow[],
canScheduleNowTransform: boolean,
transformNodes: number
) => {
// Disable schedule-now for batch transforms which have completed.
const completedBatchTransform = items.some((i: TransformListRow) => isCompletedBatchTransform(i));

return (
!canScheduleNowTransform ||
completedBatchTransform ||
items.length === 0 ||
transformNodes === 0
);
};

export interface ScheduleNowActionNameProps {
items: TransformListRow[];
forceDisable?: boolean;
transformNodes: number;
}
export const ScheduleNowActionName: FC<ScheduleNowActionNameProps> = ({
items,
forceDisable,
transformNodes,
}) => {
const { canScheduleNowTransform } = useContext(AuthorizationContext).capabilities;
const isBulkAction = items.length > 1;

// Disable schedule-now for batch transforms which have completed.
const completedBatchTransform = items.some((i: TransformListRow) => isCompletedBatchTransform(i));

let completedBatchTransformMessage;

if (isBulkAction === true) {
completedBatchTransformMessage = i18n.translate(
'xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformBulkActionToolTip',
{
defaultMessage:
'One or more transforms are completed batch transforms and cannot be scheduled now.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One or more transforms are completed batch transforms which cannot be scheduled. perhaps?

Copy link
Contributor

@szabosteve szabosteve Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'One or more transforms are completed batch transforms and cannot be scheduled now.',
'One or more transforms are completed batch transforms and cannot be scheduled to process data instantly.',

}
);
} else {
completedBatchTransformMessage = i18n.translate(
'xpack.transform.transformList.cannotScheduleNowCompleteBatchTransformToolTip',
{
defaultMessage: '{transformId} is a completed batch transform and cannot be scheduled now.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{transformId} is a completed batch transform and cannot be scheduled. ?

Copy link
Contributor

@szabosteve szabosteve Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defaultMessage: '{transformId} is a completed batch transform and cannot be scheduled now.',
defaultMessage: '{transformId} is a completed batch transform and cannot be scheduled to process data instantly.',

values: { transformId: items[0] && items[0].config.id },
}
);
}

const actionIsDisabled = isScheduleNowActionDisabled(
items,
canScheduleNowTransform,
transformNodes
);

let content: string | undefined;
if (actionIsDisabled && items.length > 0) {
if (!canScheduleNowTransform) {
content = createCapabilityFailureMessage('canScheduleNowTransform');
} else if (completedBatchTransform) {
content = completedBatchTransformMessage;
}
}

if ((forceDisable === true || actionIsDisabled) && content !== undefined) {
return (
<EuiToolTip position="top" content={content}>
<>{scheduleNowActionNameText}</>
</EuiToolTip>
);
}

return <>{scheduleNowActionNameText}</>;
};
Loading