mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* wip: add selection and actions icon * add bulk delete functionality. use existing delete action component * add start bulk action * add stop bulk action * add label for number of transforms selected * Action components only accept items array. Update endpoint calls for array param * update tests * fix translation error * update start modal translation * transformDelete to server side for synchronous looping through ids * transformsStart to server side for synchronous looping through ids * transformsStop to server side for synchronous looping through ids * change request method for delete. * update deprecated functional component type * ensure bulk actions disabled when appropriate * handle timeouts for start,stop,delete actions * rename DataFrameTransformEndpointRequest type * disable all row actions when selected items * fix localization error
This commit is contained in:
parent
2778539171
commit
cf6d86c295
27 changed files with 762 additions and 226 deletions
|
@ -111,7 +111,7 @@ export const StepCreateForm: SFC<Props> = React.memo(
|
|||
setStarted(true);
|
||||
|
||||
try {
|
||||
await ml.dataFrame.startDataFrameTransform(transformId);
|
||||
await ml.dataFrame.startDataFrameTransforms([{ id: transformId }]);
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate('xpack.ml.dataframe.stepCreateForm.startTransformSuccessMessage', {
|
||||
defaultMessage: 'Data frame transform {transformId} started successfully.',
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Data Frame: Transform List Actions <StopAction /> Minimal initialization 1`] = `
|
||||
<EuiToolTip
|
||||
content="Your license has expired. Please contact your administrator."
|
||||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
aria-label="Stop"
|
||||
color="text"
|
||||
disabled={true}
|
||||
iconSide="left"
|
||||
iconType="stop"
|
||||
onClick={[Function]}
|
||||
size="xs"
|
||||
type="button"
|
||||
>
|
||||
Stop
|
||||
</EuiButtonEmpty>
|
||||
</EuiToolTip>
|
||||
`;
|
|
@ -17,6 +17,24 @@
|
|||
animation: none !important;
|
||||
}
|
||||
}
|
||||
.mlTransformBulkActionItem {
|
||||
display: block;
|
||||
padding: $euiSizeS;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mlTransformBulkActionsBorder {
|
||||
height: 20px;
|
||||
border-right: $euiBorderThin;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 35px;
|
||||
margin: 0px 5px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.mlTransformProgressBar {
|
||||
margin-bottom: $euiSizeM;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('Data Frame: Transform List Actions <DeleteAction />', () => {
|
|||
const item: DataFrameTransformListRow = dataFrameTransformListRow;
|
||||
const props = {
|
||||
disabled: false,
|
||||
item,
|
||||
items: [item],
|
||||
deleteTransform(d: DataFrameTransformListRow) {},
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, SFC, useState } from 'react';
|
||||
import React, { Fragment, FC, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
|
@ -14,7 +14,7 @@ import {
|
|||
EUI_MODAL_CONFIRM_BUTTON,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { deleteTransform } from '../../services/transform_service';
|
||||
import { deleteTransforms } from '../../services/transform_service';
|
||||
|
||||
import {
|
||||
checkPermission,
|
||||
|
@ -24,11 +24,16 @@ import {
|
|||
import { DataFrameTransformListRow, DATA_FRAME_TRANSFORM_STATE } from './common';
|
||||
|
||||
interface DeleteActionProps {
|
||||
item: DataFrameTransformListRow;
|
||||
items: DataFrameTransformListRow[];
|
||||
forceDisable?: boolean;
|
||||
}
|
||||
|
||||
export const DeleteAction: SFC<DeleteActionProps> = ({ item }) => {
|
||||
const disabled = item.stats.state !== DATA_FRAME_TRANSFORM_STATE.STOPPED;
|
||||
export const DeleteAction: FC<DeleteActionProps> = ({ items, forceDisable }) => {
|
||||
const isBulkAction = items.length > 1;
|
||||
|
||||
const disabled = items.some(
|
||||
(i: DataFrameTransformListRow) => i.stats.state !== DATA_FRAME_TRANSFORM_STATE.STOPPED
|
||||
);
|
||||
|
||||
const canDeleteDataFrame: boolean = checkPermission('canDeleteDataFrame');
|
||||
|
||||
|
@ -37,19 +42,54 @@ export const DeleteAction: SFC<DeleteActionProps> = ({ item }) => {
|
|||
const closeModal = () => setModalVisible(false);
|
||||
const deleteAndCloseModal = () => {
|
||||
setModalVisible(false);
|
||||
deleteTransform(item);
|
||||
deleteTransforms(items);
|
||||
};
|
||||
const openModal = () => setModalVisible(true);
|
||||
|
||||
const buttonDeleteText = i18n.translate('xpack.ml.dataframe.transformList.deleteActionName', {
|
||||
defaultMessage: 'Delete',
|
||||
});
|
||||
const bulkDeleteButtonDisabledText = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.deleteBulkActionDisabledToolTipContent',
|
||||
{
|
||||
defaultMessage:
|
||||
'One or more selected data frame transforms must be stopped in order to be deleted.',
|
||||
}
|
||||
);
|
||||
const deleteButtonDisabledText = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.deleteActionDisabledToolTipContent',
|
||||
{
|
||||
defaultMessage: 'Stop the data frame transform in order to delete it.',
|
||||
}
|
||||
);
|
||||
const bulkDeleteModalTitle = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.bulkDeleteModalTitle',
|
||||
{
|
||||
defaultMessage: 'Delete {count} {count, plural, one {transform} other {transforms}}?',
|
||||
values: { count: items.length },
|
||||
}
|
||||
);
|
||||
const deleteModalTitle = i18n.translate('xpack.ml.dataframe.transformList.deleteModalTitle', {
|
||||
defaultMessage: 'Delete {transformId}',
|
||||
values: { transformId: items[0] && items[0].config.id },
|
||||
});
|
||||
const bulkDeleteModalMessage = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.bulkDeleteModalBody',
|
||||
{
|
||||
defaultMessage:
|
||||
"Are you sure you want to delete {count, plural, one {this} other {these}} {count} {count, plural, one {transform} other {transforms}}? The transform's destination index and optional Kibana index pattern will not be deleted.",
|
||||
values: { count: items.length },
|
||||
}
|
||||
);
|
||||
const deleteModalMessage = i18n.translate('xpack.ml.dataframe.transformList.deleteModalBody', {
|
||||
defaultMessage: `Are you sure you want to delete this transform? The transform's destination index and optional Kibana index pattern will not be deleted.`,
|
||||
});
|
||||
|
||||
let deleteButton = (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
color="text"
|
||||
disabled={disabled || !canDeleteDataFrame}
|
||||
disabled={forceDisable === true || disabled || !canDeleteDataFrame}
|
||||
iconType="trash"
|
||||
onClick={openModal}
|
||||
aria-label={buttonDeleteText}
|
||||
|
@ -59,20 +99,15 @@ export const DeleteAction: SFC<DeleteActionProps> = ({ item }) => {
|
|||
);
|
||||
|
||||
if (disabled || !canDeleteDataFrame) {
|
||||
let content;
|
||||
if (disabled) {
|
||||
content = isBulkAction === true ? bulkDeleteButtonDisabledText : deleteButtonDisabledText;
|
||||
} else {
|
||||
content = createPermissionFailureMessage('canStartStopDataFrame');
|
||||
}
|
||||
|
||||
deleteButton = (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
disabled
|
||||
? i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.deleteActionDisabledToolTipContent',
|
||||
{
|
||||
defaultMessage: 'Stop the data frame transform in order to delete it.',
|
||||
}
|
||||
)
|
||||
: createPermissionFailureMessage('canStartStopDataFrame')
|
||||
}
|
||||
>
|
||||
<EuiToolTip position="top" content={content}>
|
||||
{deleteButton}
|
||||
</EuiToolTip>
|
||||
);
|
||||
|
@ -84,10 +119,7 @@ export const DeleteAction: SFC<DeleteActionProps> = ({ item }) => {
|
|||
{isModalVisible && (
|
||||
<EuiOverlayMask>
|
||||
<EuiConfirmModal
|
||||
title={i18n.translate('xpack.ml.dataframe.transformList.deleteModalTitle', {
|
||||
defaultMessage: 'Delete {transformId}',
|
||||
values: { transformId: item.config.id },
|
||||
})}
|
||||
title={isBulkAction === true ? bulkDeleteModalTitle : deleteModalTitle}
|
||||
onCancel={closeModal}
|
||||
onConfirm={deleteAndCloseModal}
|
||||
cancelButtonText={i18n.translate(
|
||||
|
@ -105,11 +137,7 @@ export const DeleteAction: SFC<DeleteActionProps> = ({ item }) => {
|
|||
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
|
||||
buttonColor="danger"
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.ml.dataframe.transformList.deleteModalBody', {
|
||||
defaultMessage: `Are you sure you want to delete this transform? The transform's destination index and optional Kibana index pattern will not be deleted.`,
|
||||
})}
|
||||
</p>
|
||||
<p>{isBulkAction === true ? bulkDeleteModalMessage : deleteModalMessage}</p>
|
||||
</EuiConfirmModal>
|
||||
</EuiOverlayMask>
|
||||
)}
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('Data Frame: Transform List Actions <StartAction />', () => {
|
|||
const item: DataFrameTransformListRow = dataFrameTransformListRow;
|
||||
const props = {
|
||||
disabled: false,
|
||||
item,
|
||||
items: [item],
|
||||
startTransform(d: DataFrameTransformListRow) {},
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, SFC, useState } from 'react';
|
||||
import React, { Fragment, FC, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
|
@ -14,20 +14,26 @@ import {
|
|||
EUI_MODAL_CONFIRM_BUTTON,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { startTransform } from '../../services/transform_service';
|
||||
import { startTransforms } from '../../services/transform_service';
|
||||
|
||||
import {
|
||||
checkPermission,
|
||||
createPermissionFailureMessage,
|
||||
} from '../../../../../privilege/check_privilege';
|
||||
|
||||
import { DataFrameTransformListRow, isCompletedBatchTransform } from './common';
|
||||
import {
|
||||
DataFrameTransformListRow,
|
||||
isCompletedBatchTransform,
|
||||
DATA_FRAME_TRANSFORM_STATE,
|
||||
} from './common';
|
||||
|
||||
interface StartActionProps {
|
||||
item: DataFrameTransformListRow;
|
||||
items: DataFrameTransformListRow[];
|
||||
forceDisable?: boolean;
|
||||
}
|
||||
|
||||
export const StartAction: SFC<StartActionProps> = ({ item }) => {
|
||||
export const StartAction: FC<StartActionProps> = ({ items, forceDisable }) => {
|
||||
const isBulkAction = items.length > 1;
|
||||
const canStartStopDataFrameTransform: boolean = checkPermission('canStartStopDataFrame');
|
||||
|
||||
const [isModalVisible, setModalVisible] = useState(false);
|
||||
|
@ -35,7 +41,7 @@ export const StartAction: SFC<StartActionProps> = ({ item }) => {
|
|||
const closeModal = () => setModalVisible(false);
|
||||
const startAndCloseModal = () => {
|
||||
setModalVisible(false);
|
||||
startTransform(item);
|
||||
startTransforms(items);
|
||||
};
|
||||
const openModal = () => setModalVisible(true);
|
||||
|
||||
|
@ -44,13 +50,56 @@ export const StartAction: SFC<StartActionProps> = ({ item }) => {
|
|||
});
|
||||
|
||||
// Disable start for batch transforms which have completed.
|
||||
const completedBatchTransform = isCompletedBatchTransform(item);
|
||||
const completedBatchTransform = items.some((i: DataFrameTransformListRow) =>
|
||||
isCompletedBatchTransform(i)
|
||||
);
|
||||
// Disable start action if one of the transforms is already started or trying to restart will throw error
|
||||
const startedTransform = items.some(
|
||||
(i: DataFrameTransformListRow) => i.stats.state === DATA_FRAME_TRANSFORM_STATE.STARTED
|
||||
);
|
||||
|
||||
let startedTransformMessage;
|
||||
let completedBatchTransformMessage;
|
||||
|
||||
if (isBulkAction === true) {
|
||||
startedTransformMessage = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.startedTransformBulkToolTip',
|
||||
{
|
||||
defaultMessage: 'One or more selected data frame transforms is already started.',
|
||||
}
|
||||
);
|
||||
completedBatchTransformMessage = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.completeBatchTransformBulkActionToolTip',
|
||||
{
|
||||
defaultMessage:
|
||||
'One or more selected data frame transforms is a completed batch transform and cannot be restarted.',
|
||||
}
|
||||
);
|
||||
} else {
|
||||
startedTransformMessage = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.startedTransformToolTip',
|
||||
{
|
||||
defaultMessage: '{transformId} is already started.',
|
||||
values: { transformId: items[0] && items[0].config.id },
|
||||
}
|
||||
);
|
||||
completedBatchTransformMessage = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.completeBatchTransformToolTip',
|
||||
{
|
||||
defaultMessage: '{transformId} is a completed batch transform and cannot be restarted.',
|
||||
values: { transformId: items[0] && items[0].config.id },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const actionIsDisabled =
|
||||
!canStartStopDataFrameTransform || completedBatchTransform || startedTransform;
|
||||
|
||||
let startButton = (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
color="text"
|
||||
disabled={!canStartStopDataFrameTransform || completedBatchTransform}
|
||||
disabled={forceDisable === true || actionIsDisabled}
|
||||
iconType="play"
|
||||
onClick={openModal}
|
||||
aria-label={buttonStartText}
|
||||
|
@ -59,35 +108,42 @@ export const StartAction: SFC<StartActionProps> = ({ item }) => {
|
|||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
if (!canStartStopDataFrameTransform || completedBatchTransform) {
|
||||
if (actionIsDisabled) {
|
||||
let content;
|
||||
if (!canStartStopDataFrameTransform) {
|
||||
content = createPermissionFailureMessage('canStartStopDataFrame');
|
||||
} else if (completedBatchTransform) {
|
||||
content = completedBatchTransformMessage;
|
||||
} else if (startedTransform) {
|
||||
content = startedTransformMessage;
|
||||
}
|
||||
|
||||
startButton = (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
!canStartStopDataFrameTransform
|
||||
? createPermissionFailureMessage('canStartStopDataFrame')
|
||||
: i18n.translate('xpack.ml.dataframe.transformList.completeBatchTransformToolTip', {
|
||||
defaultMessage:
|
||||
'{transformId} is a completed batch transform and cannot be restarted.',
|
||||
values: { transformId: item.config.id },
|
||||
})
|
||||
}
|
||||
>
|
||||
<EuiToolTip position="top" content={content}>
|
||||
{startButton}
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
const bulkStartModalTitle = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.bulkStartModalTitle',
|
||||
{
|
||||
defaultMessage: 'Start {count} {count, plural, one {transform} other {transforms}}?',
|
||||
values: { count: items && items.length },
|
||||
}
|
||||
);
|
||||
const startModalTitle = i18n.translate('xpack.ml.dataframe.transformList.startModalTitle', {
|
||||
defaultMessage: 'Start {transformId}',
|
||||
values: { transformId: items[0] && items[0].config.id },
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{startButton}
|
||||
{isModalVisible && (
|
||||
<EuiOverlayMask>
|
||||
<EuiConfirmModal
|
||||
title={i18n.translate('xpack.ml.dataframe.transformList.startModalTitle', {
|
||||
defaultMessage: 'Start {transformId}',
|
||||
values: { transformId: item.config.id },
|
||||
})}
|
||||
title={isBulkAction === true ? bulkStartModalTitle : startModalTitle}
|
||||
onCancel={closeModal}
|
||||
onConfirm={startAndCloseModal}
|
||||
cancelButtonText={i18n.translate(
|
||||
|
@ -108,7 +164,8 @@ export const StartAction: SFC<StartActionProps> = ({ item }) => {
|
|||
<p>
|
||||
{i18n.translate('xpack.ml.dataframe.transformList.startModalBody', {
|
||||
defaultMessage:
|
||||
'A data frame transform will increase search and indexing load in your cluster. Please stop the transform if excessive load is experienced. Are you sure you want to start this transform?',
|
||||
'A data frame transform will increase search and indexing load in your cluster. Please stop the transform if excessive load is experienced. Are you sure you want to start {count, plural, one {this} other {these}} {count} {count, plural, one {transform} other {transforms}}?',
|
||||
values: { count: items.length },
|
||||
})}
|
||||
</p>
|
||||
</EuiConfirmModal>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { DataFrameTransformListRow } from './common';
|
||||
import { StopAction } from './action_stop';
|
||||
|
||||
import dataFrameTransformListRow from './__mocks__/data_frame_transform_list_row.json';
|
||||
|
||||
describe('Data Frame: Transform List Actions <StopAction />', () => {
|
||||
test('Minimal initialization', () => {
|
||||
const item: DataFrameTransformListRow = dataFrameTransformListRow;
|
||||
const props = {
|
||||
disabled: false,
|
||||
items: [item],
|
||||
stopTransform(d: DataFrameTransformListRow) {},
|
||||
};
|
||||
|
||||
const wrapper = shallow(<StopAction {...props} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { DataFrameTransformListRow, DATA_FRAME_TRANSFORM_STATE } from './common';
|
||||
import {
|
||||
checkPermission,
|
||||
createPermissionFailureMessage,
|
||||
} from '../../../../../privilege/check_privilege';
|
||||
import { stopTransforms } from '../../services/transform_service';
|
||||
|
||||
interface StopActionProps {
|
||||
items: DataFrameTransformListRow[];
|
||||
forceDisable?: boolean;
|
||||
}
|
||||
|
||||
export const StopAction: FC<StopActionProps> = ({ items, forceDisable }) => {
|
||||
const isBulkAction = items.length > 1;
|
||||
const canStartStopDataFrame: boolean = checkPermission('canStartStopDataFrame');
|
||||
const buttonStopText = i18n.translate('xpack.ml.dataframe.transformList.stopActionName', {
|
||||
defaultMessage: 'Stop',
|
||||
});
|
||||
|
||||
// Disable stop action if one of the transforms is stopped already
|
||||
const stoppedTransform = items.some(
|
||||
(i: DataFrameTransformListRow) => i.stats.state === DATA_FRAME_TRANSFORM_STATE.STOPPED
|
||||
);
|
||||
|
||||
let stoppedTransformMessage;
|
||||
if (isBulkAction === true) {
|
||||
stoppedTransformMessage = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.stoppedTransformBulkToolTip',
|
||||
{
|
||||
defaultMessage: 'One or more selected data frame transforms is already stopped.',
|
||||
}
|
||||
);
|
||||
} else {
|
||||
stoppedTransformMessage = i18n.translate(
|
||||
'xpack.ml.dataframe.transformList.stoppedTransformToolTip',
|
||||
{
|
||||
defaultMessage: '{transformId} is already stopped.',
|
||||
values: { transformId: items[0] && items[0].config.id },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
stopTransforms(items);
|
||||
};
|
||||
|
||||
const stopButton = (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
color="text"
|
||||
disabled={forceDisable === true || !canStartStopDataFrame || stoppedTransform === true}
|
||||
iconType="stop"
|
||||
onClick={handleStop}
|
||||
aria-label={buttonStopText}
|
||||
>
|
||||
{buttonStopText}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
if (!canStartStopDataFrame || stoppedTransform) {
|
||||
return (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={
|
||||
!canStartStopDataFrame
|
||||
? createPermissionFailureMessage('canStartStopDataFrame')
|
||||
: stoppedTransformMessage
|
||||
}
|
||||
>
|
||||
{stopButton}
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
return stopButton;
|
||||
};
|
|
@ -8,7 +8,7 @@ import { getActions } from './actions';
|
|||
|
||||
describe('Data Frame: Transform List Actions', () => {
|
||||
test('getActions()', () => {
|
||||
const actions = getActions();
|
||||
const actions = getActions({ forceDisable: false });
|
||||
|
||||
expect(actions).toHaveLength(2);
|
||||
expect(actions[0].isPrimary).toBeTruthy();
|
||||
|
|
|
@ -5,64 +5,25 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
checkPermission,
|
||||
createPermissionFailureMessage,
|
||||
} from '../../../../../privilege/check_privilege';
|
||||
|
||||
import { DataFrameTransformListRow, DATA_FRAME_TRANSFORM_STATE } from './common';
|
||||
import { stopTransform } from '../../services/transform_service';
|
||||
|
||||
import { StartAction } from './action_start';
|
||||
import { StopAction } from './action_stop';
|
||||
import { DeleteAction } from './action_delete';
|
||||
|
||||
export const getActions = () => {
|
||||
const canStartStopDataFrame: boolean = checkPermission('canStartStopDataFrame');
|
||||
|
||||
export const getActions = ({ forceDisable }: { forceDisable: boolean }) => {
|
||||
return [
|
||||
{
|
||||
isPrimary: true,
|
||||
render: (item: DataFrameTransformListRow) => {
|
||||
if (item.stats.state === DATA_FRAME_TRANSFORM_STATE.STOPPED) {
|
||||
return <StartAction item={item} />;
|
||||
return <StartAction items={[item]} forceDisable={forceDisable} />;
|
||||
}
|
||||
|
||||
const buttonStopText = i18n.translate('xpack.ml.dataframe.transformList.stopActionName', {
|
||||
defaultMessage: 'Stop',
|
||||
});
|
||||
|
||||
const stopButton = (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
color="text"
|
||||
disabled={!canStartStopDataFrame}
|
||||
iconType="stop"
|
||||
onClick={() => stopTransform(item)}
|
||||
aria-label={buttonStopText}
|
||||
>
|
||||
{buttonStopText}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
if (!canStartStopDataFrame) {
|
||||
return (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={createPermissionFailureMessage('canStartStopDataFrame')}
|
||||
>
|
||||
{stopButton}
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
return stopButton;
|
||||
return <StopAction items={[item]} forceDisable={forceDisable} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
render: (item: DataFrameTransformListRow) => {
|
||||
return <DeleteAction item={item} />;
|
||||
return <DeleteAction items={[item]} forceDisable={forceDisable} />;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -8,7 +8,7 @@ import { getColumns } from './columns';
|
|||
|
||||
describe('Data Frame: Job List Columns', () => {
|
||||
test('getColumns()', () => {
|
||||
const columns = getColumns([], () => {});
|
||||
const columns = getColumns([], () => {}, []);
|
||||
|
||||
expect(columns).toHaveLength(9);
|
||||
expect(columns[0].isExpander).toBeTruthy();
|
||||
|
|
|
@ -61,9 +61,10 @@ export const getTaskStateBadge = (
|
|||
|
||||
export const getColumns = (
|
||||
expandedRowItemIds: DataFrameTransformId[],
|
||||
setExpandedRowItemIds: React.Dispatch<React.SetStateAction<DataFrameTransformId[]>>
|
||||
setExpandedRowItemIds: React.Dispatch<React.SetStateAction<DataFrameTransformId[]>>,
|
||||
transformSelection: DataFrameTransformListRow[]
|
||||
) => {
|
||||
const actions = getActions();
|
||||
const actions = getActions({ forceDisable: transformSelection.length > 0 });
|
||||
|
||||
function toggleDetails(item: DataFrameTransformListRow) {
|
||||
const index = expandedRowItemIds.indexOf(item.config.id);
|
||||
|
|
|
@ -105,6 +105,20 @@ export interface DataFrameTransformListRow {
|
|||
stats: DataFrameTransformStats;
|
||||
}
|
||||
|
||||
export interface DataFrameTransformEndpointRequest {
|
||||
id: DataFrameTransformId;
|
||||
state?: DATA_FRAME_TRANSFORM_STATE;
|
||||
}
|
||||
|
||||
export interface ResultData {
|
||||
success: boolean;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
export interface DataFrameTransformEndpointResult {
|
||||
[key: string]: ResultData;
|
||||
}
|
||||
|
||||
// Used to pass on attribute names to table columns
|
||||
export enum DataFrameTransformListColumn {
|
||||
configDestIndex = 'config.dest.index',
|
||||
|
|
|
@ -8,7 +8,16 @@ import React, { Fragment, SFC, useState } from 'react';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiBadge, EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, SortDirection } from '@elastic/eui';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiCallOut,
|
||||
EuiEmptyPrompt,
|
||||
EuiPopover,
|
||||
EuiTitle,
|
||||
SortDirection,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
DataFrameTransformId,
|
||||
|
@ -17,6 +26,9 @@ import {
|
|||
} from '../../../../common';
|
||||
import { checkPermission } from '../../../../../privilege/check_privilege';
|
||||
import { getTaskStateBadge } from './columns';
|
||||
import { DeleteAction } from './action_delete';
|
||||
import { StartAction } from './action_start';
|
||||
import { StopAction } from './action_stop';
|
||||
|
||||
import {
|
||||
DataFrameTransformListColumn,
|
||||
|
@ -67,6 +79,9 @@ export const DataFrameTransformList: SFC = () => {
|
|||
const [filteredTransforms, setFilteredTransforms] = useState<DataFrameTransformListRow[]>([]);
|
||||
const [expandedRowItemIds, setExpandedRowItemIds] = useState<DataFrameTransformId[]>([]);
|
||||
|
||||
const [transformSelection, setTransformSelection] = useState<DataFrameTransformListRow[]>([]);
|
||||
const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false);
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<any>(undefined);
|
||||
const [searchError, setSearchError] = useState<any>(undefined);
|
||||
|
||||
|
@ -218,7 +233,7 @@ export const DataFrameTransformList: SFC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
const columns = getColumns(expandedRowItemIds, setExpandedRowItemIds);
|
||||
const columns = getColumns(expandedRowItemIds, setExpandedRowItemIds, transformSelection);
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
|
@ -237,7 +252,66 @@ export const DataFrameTransformList: SFC = () => {
|
|||
hidePerPageOptions: false,
|
||||
};
|
||||
|
||||
const bulkActionMenuItems = [
|
||||
<div key="startAction" className="mlTransformBulkActionItem">
|
||||
<StartAction items={transformSelection} />
|
||||
</div>,
|
||||
<div key="stopAction" className="mlTransformBulkActionItem">
|
||||
<StopAction items={transformSelection} />
|
||||
</div>,
|
||||
<div key="deleteAction" className="mlTransformBulkActionItem">
|
||||
<DeleteAction items={transformSelection} />
|
||||
</div>,
|
||||
];
|
||||
|
||||
const renderToolsLeft = () => {
|
||||
const buttonIcon = (
|
||||
<EuiButtonIcon
|
||||
size="s"
|
||||
iconType="gear"
|
||||
color="text"
|
||||
onClick={() => {
|
||||
setIsActionsMenuOpen(true);
|
||||
}}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.ml.dataframe.multiTransformActionsMenu.managementActionsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Management actions',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
const bulkActionIcon = (
|
||||
<EuiPopover
|
||||
key="bulkActionIcon"
|
||||
id="transformBulkActionsMenu"
|
||||
button={buttonIcon}
|
||||
isOpen={isActionsMenuOpen}
|
||||
closePopover={() => setIsActionsMenuOpen(false)}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="rightUp"
|
||||
>
|
||||
{bulkActionMenuItems}
|
||||
</EuiPopover>
|
||||
);
|
||||
|
||||
return [
|
||||
<EuiTitle key="selectedText" size="s">
|
||||
<h3>
|
||||
{i18n.translate('xpack.ml.dataframe.multiTransformActionsMenu.transformsCount', {
|
||||
defaultMessage: '{count} {count, plural, one {transform} other {transforms}} selected',
|
||||
values: { count: transformSelection.length },
|
||||
})}
|
||||
</h3>
|
||||
</EuiTitle>,
|
||||
<div key="bulkActionsBorder" className="mlTransformBulkActionsBorder" />,
|
||||
bulkActionIcon,
|
||||
];
|
||||
};
|
||||
|
||||
const search = {
|
||||
toolsLeft: transformSelection.length > 0 ? renderToolsLeft() : undefined,
|
||||
onChange: onQueryChange,
|
||||
box: {
|
||||
incremental: true,
|
||||
|
@ -288,6 +362,10 @@ export const DataFrameTransformList: SFC = () => {
|
|||
setSortDirection(direction);
|
||||
};
|
||||
|
||||
const selection = {
|
||||
onSelectionChange: (selected: DataFrameTransformListRow[]) => setTransformSelection(selected),
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ProgressBar isLoading={isLoading} />
|
||||
|
@ -303,6 +381,7 @@ export const DataFrameTransformList: SFC = () => {
|
|||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
onChange={onTableChange}
|
||||
pagination={pagination}
|
||||
selection={selection}
|
||||
sorting={sorting}
|
||||
search={search}
|
||||
data-test-subj="mlDataFramesTableTransforms"
|
||||
|
|
|
@ -7,34 +7,45 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { ml } from '../../../../../services/ml_api_service';
|
||||
|
||||
import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common';
|
||||
|
||||
import {
|
||||
DATA_FRAME_TRANSFORM_STATE,
|
||||
DataFrameTransformListRow,
|
||||
DataFrameTransformEndpointRequest,
|
||||
DataFrameTransformEndpointResult,
|
||||
} from '../../components/transform_list/common';
|
||||
// @ts-ignore no declaration file
|
||||
import { mlMessageBarService } from '../../../../../../public/components/messagebar/messagebar_service';
|
||||
|
||||
export const deleteTransform = async (d: DataFrameTransformListRow) => {
|
||||
try {
|
||||
if (d.stats.state === DATA_FRAME_TRANSFORM_STATE.FAILED) {
|
||||
await ml.dataFrame.stopDataFrameTransform(d.config.id, true, true);
|
||||
export const deleteTransforms = async (dataFrames: DataFrameTransformListRow[]) => {
|
||||
const dataFramesInfo: DataFrameTransformEndpointRequest[] = dataFrames.map(df => ({
|
||||
id: df.config.id,
|
||||
state: df.stats.state,
|
||||
}));
|
||||
const results: DataFrameTransformEndpointResult = await ml.dataFrame.deleteDataFrameTransforms(
|
||||
dataFramesInfo
|
||||
);
|
||||
|
||||
for (const transformId in results) {
|
||||
// hasOwnProperty check to ensure only properties on object itself, and not its prototypes
|
||||
if (results.hasOwnProperty(transformId)) {
|
||||
if (results[transformId].success === true) {
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.deleteTransformSuccessMessage', {
|
||||
defaultMessage: 'Data frame transform {transformId} deleted successfully.',
|
||||
values: { transformId },
|
||||
})
|
||||
);
|
||||
} else {
|
||||
toastNotifications.addDanger(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.deleteTransformErrorMessage', {
|
||||
defaultMessage: 'An error occurred deleting the data frame transform {transformId}',
|
||||
values: { transformId },
|
||||
})
|
||||
);
|
||||
mlMessageBarService.notify.error(results[transformId].error);
|
||||
}
|
||||
}
|
||||
await ml.dataFrame.deleteDataFrameTransform(d.config.id);
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.deleteTransformSuccessMessage', {
|
||||
defaultMessage: 'Data frame transform {transformId} deleted successfully.',
|
||||
values: { transformId: d.config.id },
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.deleteTransformErrorMessage', {
|
||||
defaultMessage:
|
||||
'An error occurred deleting the data frame transform {transformId}: {error}',
|
||||
values: { transformId: d.config.id, error: JSON.stringify(e) },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
*/
|
||||
|
||||
export { getTransformsFactory } from './get_transforms';
|
||||
export { deleteTransform } from './delete_transform';
|
||||
export { startTransform } from './start_transform';
|
||||
export { stopTransform } from './stop_transform';
|
||||
export { deleteTransforms } from './delete_transform';
|
||||
export { startTransforms } from './start_transform';
|
||||
export { stopTransforms } from './stop_transform';
|
||||
|
|
|
@ -11,30 +11,43 @@ import { ml } from '../../../../../services/ml_api_service';
|
|||
import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common';
|
||||
|
||||
import {
|
||||
DATA_FRAME_TRANSFORM_STATE,
|
||||
DataFrameTransformListRow,
|
||||
DataFrameTransformEndpointRequest,
|
||||
DataFrameTransformEndpointResult,
|
||||
} from '../../components/transform_list/common';
|
||||
// @ts-ignore no declaration file
|
||||
import { mlMessageBarService } from '../../../../../../public/components/messagebar/messagebar_service';
|
||||
|
||||
export const startTransform = async (d: DataFrameTransformListRow) => {
|
||||
try {
|
||||
await ml.dataFrame.startDataFrameTransform(
|
||||
d.config.id,
|
||||
d.stats.state === DATA_FRAME_TRANSFORM_STATE.FAILED
|
||||
);
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.startTransformSuccessMessage', {
|
||||
defaultMessage: 'Data frame transform {transformId} started successfully.',
|
||||
values: { transformId: d.config.id },
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.startTransformErrorMessage', {
|
||||
defaultMessage:
|
||||
'An error occurred starting the data frame transform {transformId}: {error}',
|
||||
values: { transformId: d.config.id, error: JSON.stringify(e) },
|
||||
})
|
||||
);
|
||||
export const startTransforms = async (dataFrames: DataFrameTransformListRow[]) => {
|
||||
const dataFramesInfo: DataFrameTransformEndpointRequest[] = dataFrames.map(df => ({
|
||||
id: df.config.id,
|
||||
state: df.stats.state,
|
||||
}));
|
||||
const results: DataFrameTransformEndpointResult = await ml.dataFrame.startDataFrameTransforms(
|
||||
dataFramesInfo
|
||||
);
|
||||
|
||||
for (const transformId in results) {
|
||||
// hasOwnProperty check to ensure only properties on object itself, and not its prototypes
|
||||
if (results.hasOwnProperty(transformId)) {
|
||||
if (results[transformId].success === true) {
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.startTransformSuccessMessage', {
|
||||
defaultMessage: 'Data frame transform {transformId} started successfully.',
|
||||
values: { transformId },
|
||||
})
|
||||
);
|
||||
} else {
|
||||
toastNotifications.addDanger(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.startTransformErrorMessage', {
|
||||
defaultMessage: 'An error occurred starting the data frame transform {transformId}',
|
||||
values: { transformId },
|
||||
})
|
||||
);
|
||||
mlMessageBarService.notify.error(results[transformId].error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH);
|
||||
};
|
||||
|
|
|
@ -11,31 +11,43 @@ import { ml } from '../../../../../services/ml_api_service';
|
|||
import { refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../../../../common';
|
||||
|
||||
import {
|
||||
DATA_FRAME_TRANSFORM_STATE,
|
||||
DataFrameTransformListRow,
|
||||
DataFrameTransformEndpointRequest,
|
||||
DataFrameTransformEndpointResult,
|
||||
} from '../../components/transform_list/common';
|
||||
// @ts-ignore no declaration file
|
||||
import { mlMessageBarService } from '../../../../../../public/components/messagebar/messagebar_service';
|
||||
|
||||
export const stopTransform = async (d: DataFrameTransformListRow) => {
|
||||
try {
|
||||
await ml.dataFrame.stopDataFrameTransform(
|
||||
d.config.id,
|
||||
d.stats.state === DATA_FRAME_TRANSFORM_STATE.FAILED,
|
||||
true
|
||||
);
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.stopTransformSuccessMessage', {
|
||||
defaultMessage: 'Data frame transform {transformId} stopped successfully.',
|
||||
values: { transformId: d.config.id },
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.stopTransformErrorMessage', {
|
||||
defaultMessage:
|
||||
'An error occurred stopping the data frame transform {transformId}: {error}',
|
||||
values: { transformId: d.config.id, error: JSON.stringify(e) },
|
||||
})
|
||||
);
|
||||
export const stopTransforms = async (dataFrames: DataFrameTransformListRow[]) => {
|
||||
const dataFramesInfo: DataFrameTransformEndpointRequest[] = dataFrames.map(df => ({
|
||||
id: df.config.id,
|
||||
state: df.stats.state,
|
||||
}));
|
||||
const results: DataFrameTransformEndpointResult = await ml.dataFrame.stopDataFrameTransforms(
|
||||
dataFramesInfo
|
||||
);
|
||||
|
||||
for (const transformId in results) {
|
||||
// hasOwnProperty check to ensure only properties on object itself, and not its prototypes
|
||||
if (results.hasOwnProperty(transformId)) {
|
||||
if (results[transformId].success === true) {
|
||||
toastNotifications.addSuccess(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.stopTransformSuccessMessage', {
|
||||
defaultMessage: 'Data frame transform {transformId} stopped successfully.',
|
||||
values: { transformId },
|
||||
})
|
||||
);
|
||||
} else {
|
||||
toastNotifications.addDanger(
|
||||
i18n.translate('xpack.ml.dataframe.transformList.stopTransformErrorMessage', {
|
||||
defaultMessage: 'An error occurred stopping the data frame transform {transformId}',
|
||||
values: { transformId },
|
||||
})
|
||||
);
|
||||
mlMessageBarService.notify.error(results[transformId].error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshTransformList$.next(REFRESH_TRANSFORM_LIST_STATE.REFRESH);
|
||||
};
|
||||
|
|
|
@ -40,10 +40,13 @@ export const dataFrame = {
|
|||
data: transformConfig
|
||||
});
|
||||
},
|
||||
deleteDataFrameTransform(transformId) {
|
||||
deleteDataFrameTransforms(transformsInfo) {
|
||||
return http({
|
||||
url: `${basePath}/_data_frame/transforms/${transformId}`,
|
||||
method: 'DELETE',
|
||||
url: `${basePath}/_data_frame/transforms/delete_transforms`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
transformsInfo
|
||||
}
|
||||
});
|
||||
},
|
||||
getDataFrameTransformsPreview(obj) {
|
||||
|
@ -53,16 +56,22 @@ export const dataFrame = {
|
|||
data: obj
|
||||
});
|
||||
},
|
||||
startDataFrameTransform(transformId, force = false) {
|
||||
startDataFrameTransforms(transformsInfo) {
|
||||
return http({
|
||||
url: `${basePath}/_data_frame/transforms/${transformId}/_start?force=${force}`,
|
||||
url: `${basePath}/_data_frame/transforms/start_transforms`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
transformsInfo,
|
||||
}
|
||||
});
|
||||
},
|
||||
stopDataFrameTransform(transformId, force = false, waitForCompletion = false) {
|
||||
stopDataFrameTransforms(transformsInfo) {
|
||||
return http({
|
||||
url: `${basePath}/_data_frame/transforms/${transformId}/_stop?force=${force}&wait_for_completion=${waitForCompletion}`,
|
||||
url: `${basePath}/_data_frame/transforms/stop_transforms`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
transformsInfo,
|
||||
}
|
||||
});
|
||||
},
|
||||
getTransformAuditMessages(transformId) {
|
||||
|
|
|
@ -8,6 +8,10 @@ import { Annotation } from '../../../common/types/annotations';
|
|||
import { AggFieldNamePair } from '../../../common/types/fields';
|
||||
import { ExistingJobsAndGroups } from '../job_service';
|
||||
import { PrivilegesResponse } from '../../../common/types/privileges';
|
||||
import {
|
||||
DataFrameTransformEndpointRequest,
|
||||
DataFrameTransformEndpointResult,
|
||||
} from '../../data_frame/pages/transform_management/components/transform_list/common';
|
||||
|
||||
// TODO This is not a complete representation of all methods of `ml.*`.
|
||||
// It just satisfies needs for other parts of the code area which use
|
||||
|
@ -47,14 +51,16 @@ declare interface Ml {
|
|||
getDataFrameTransforms(jobId?: string): Promise<any>;
|
||||
getDataFrameTransformsStats(jobId?: string): Promise<any>;
|
||||
createDataFrameTransform(jobId: string, jobConfig: any): Promise<any>;
|
||||
deleteDataFrameTransform(jobId: string): Promise<any>;
|
||||
deleteDataFrameTransforms(
|
||||
jobsData: DataFrameTransformEndpointRequest[]
|
||||
): Promise<DataFrameTransformEndpointResult>;
|
||||
getDataFrameTransformsPreview(payload: any): Promise<any>;
|
||||
startDataFrameTransform(jobId: string, force?: boolean): Promise<any>;
|
||||
stopDataFrameTransform(
|
||||
jobId: string,
|
||||
force?: boolean,
|
||||
waitForCompletion?: boolean
|
||||
): Promise<any>;
|
||||
startDataFrameTransforms(
|
||||
jobsData: DataFrameTransformEndpointRequest[]
|
||||
): Promise<DataFrameTransformEndpointResult>;
|
||||
stopDataFrameTransforms(
|
||||
jobsData: DataFrameTransformEndpointRequest[]
|
||||
): Promise<DataFrameTransformEndpointResult>;
|
||||
getTransformAuditMessages(transformId: string): Promise<any>;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// @ts-ignore no declaration file for module
|
||||
export { isRequestTimeout } from '../job_service/error_utils';
|
||||
import {
|
||||
DataFrameTransformEndpointRequest,
|
||||
DataFrameTransformEndpointResult,
|
||||
} from '../../../public/data_frame/pages/transform_management/components/transform_list/common';
|
||||
|
||||
interface Params {
|
||||
results: DataFrameTransformEndpointResult;
|
||||
id: string;
|
||||
items: DataFrameTransformEndpointRequest[];
|
||||
action: string;
|
||||
}
|
||||
|
||||
// populate a results object with timeout errors for the ids which haven't already been set
|
||||
export function fillResultsWithTimeouts({ results, id, items, action }: Params) {
|
||||
const extra =
|
||||
items.length - Object.keys(results).length > 1
|
||||
? i18n.translate('xpack.ml.models.transformService.allOtherRequestsCancelledDescription', {
|
||||
defaultMessage: 'All other requests cancelled.',
|
||||
})
|
||||
: '';
|
||||
|
||||
const error = {
|
||||
response: {
|
||||
error: {
|
||||
root_cause: [
|
||||
{
|
||||
reason: i18n.translate(
|
||||
'xpack.ml.models.transformService.requestToActionTimedOutErrorMessage',
|
||||
{
|
||||
defaultMessage: `Request to {action} '{id}' timed out. {extra}`,
|
||||
values: {
|
||||
id,
|
||||
action,
|
||||
extra,
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newResults: DataFrameTransformEndpointResult = {};
|
||||
|
||||
return items.reduce((accumResults, currentVal) => {
|
||||
if (results[currentVal.id] === undefined) {
|
||||
accumResults[currentVal.id] = {
|
||||
success: false,
|
||||
error,
|
||||
};
|
||||
} else {
|
||||
accumResults[currentVal.id] = results[currentVal.id];
|
||||
}
|
||||
return accumResults;
|
||||
}, newResults);
|
||||
}
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
|
||||
export { transformAuditMessagesProvider } from './transform_audit_messages';
|
||||
export { transformServiceProvider } from './transforms';
|
||||
|
|
147
x-pack/legacy/plugins/ml/server/models/data_frame/transforms.ts
Normal file
147
x-pack/legacy/plugins/ml/server/models/data_frame/transforms.ts
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { callWithRequestType } from '../../../common/types/kibana';
|
||||
import {
|
||||
DATA_FRAME_TRANSFORM_STATE,
|
||||
DataFrameTransformEndpointRequest,
|
||||
DataFrameTransformEndpointResult,
|
||||
} from '../../../public/data_frame/pages/transform_management/components/transform_list/common';
|
||||
import { DataFrameTransformId } from '../../../public/data_frame/common/transform';
|
||||
import { isRequestTimeout, fillResultsWithTimeouts } from './error_utils';
|
||||
|
||||
enum TRANSFORM_ACTIONS {
|
||||
STOP = 'stop',
|
||||
START = 'start',
|
||||
DELETE = 'delete',
|
||||
}
|
||||
|
||||
interface StartStopOptions {
|
||||
transformId: DataFrameTransformId;
|
||||
force: boolean;
|
||||
waitForCompletion?: boolean;
|
||||
}
|
||||
|
||||
export function transformServiceProvider(callWithRequest: callWithRequestType) {
|
||||
async function deleteTransform(transformId: DataFrameTransformId) {
|
||||
return callWithRequest('ml.deleteDataFrameTransform', { transformId });
|
||||
}
|
||||
|
||||
async function stopTransform(options: StartStopOptions) {
|
||||
return callWithRequest('ml.stopDataFrameTransform', options);
|
||||
}
|
||||
|
||||
async function startTransform(options: StartStopOptions) {
|
||||
return callWithRequest('ml.startDataFrameTransform', options);
|
||||
}
|
||||
|
||||
async function deleteTransforms(transformsInfo: DataFrameTransformEndpointRequest[]) {
|
||||
const results: DataFrameTransformEndpointResult = {};
|
||||
|
||||
for (const transformInfo of transformsInfo) {
|
||||
const transformId = transformInfo.id;
|
||||
try {
|
||||
if (transformInfo.state === DATA_FRAME_TRANSFORM_STATE.FAILED) {
|
||||
try {
|
||||
await stopTransform({
|
||||
transformId,
|
||||
force: true,
|
||||
waitForCompletion: true,
|
||||
});
|
||||
} catch (e) {
|
||||
if (isRequestTimeout(e)) {
|
||||
return fillResultsWithTimeouts({
|
||||
results,
|
||||
id: transformId,
|
||||
items: transformsInfo,
|
||||
action: TRANSFORM_ACTIONS.DELETE,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await deleteTransform(transformId);
|
||||
results[transformId] = { success: true };
|
||||
} catch (e) {
|
||||
if (isRequestTimeout(e)) {
|
||||
return fillResultsWithTimeouts({
|
||||
results,
|
||||
id: transformInfo.id,
|
||||
items: transformsInfo,
|
||||
action: TRANSFORM_ACTIONS.DELETE,
|
||||
});
|
||||
}
|
||||
results[transformId] = { success: false, error: JSON.stringify(e) };
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async function startTransforms(transformsInfo: DataFrameTransformEndpointRequest[]) {
|
||||
const results: DataFrameTransformEndpointResult = {};
|
||||
|
||||
for (const transformInfo of transformsInfo) {
|
||||
const transformId = transformInfo.id;
|
||||
try {
|
||||
await startTransform({
|
||||
transformId,
|
||||
force:
|
||||
transformInfo.state !== undefined
|
||||
? transformInfo.state === DATA_FRAME_TRANSFORM_STATE.FAILED
|
||||
: false,
|
||||
});
|
||||
results[transformId] = { success: true };
|
||||
} catch (e) {
|
||||
if (isRequestTimeout(e)) {
|
||||
return fillResultsWithTimeouts({
|
||||
results,
|
||||
id: transformId,
|
||||
items: transformsInfo,
|
||||
action: TRANSFORM_ACTIONS.START,
|
||||
});
|
||||
}
|
||||
results[transformId] = { success: false, error: JSON.stringify(e) };
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async function stopTransforms(transformsInfo: DataFrameTransformEndpointRequest[]) {
|
||||
const results: DataFrameTransformEndpointResult = {};
|
||||
|
||||
for (const transformInfo of transformsInfo) {
|
||||
const transformId = transformInfo.id;
|
||||
try {
|
||||
await stopTransform({
|
||||
transformId,
|
||||
force:
|
||||
transformInfo.state !== undefined
|
||||
? transformInfo.state === DATA_FRAME_TRANSFORM_STATE.FAILED
|
||||
: false,
|
||||
waitForCompletion: true,
|
||||
});
|
||||
results[transformId] = { success: true };
|
||||
} catch (e) {
|
||||
if (isRequestTimeout(e)) {
|
||||
return fillResultsWithTimeouts({
|
||||
results,
|
||||
id: transformId,
|
||||
items: transformsInfo,
|
||||
action: TRANSFORM_ACTIONS.STOP,
|
||||
});
|
||||
}
|
||||
results[transformId] = { success: false, error: JSON.stringify(e) };
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
return {
|
||||
deleteTransforms,
|
||||
startTransforms,
|
||||
stopTransforms,
|
||||
};
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
import { callWithRequestFactory } from '../client/call_with_request_factory';
|
||||
import { wrapError } from '../client/errors';
|
||||
import { transformAuditMessagesProvider } from '../models/data_frame/transform_audit_messages';
|
||||
import { transformServiceProvider } from '../models/data_frame';
|
||||
|
||||
export function dataFrameRoutes({ commonRouteConfig, elasticsearchPlugin, route }) {
|
||||
|
||||
|
@ -79,12 +80,13 @@ export function dataFrameRoutes({ commonRouteConfig, elasticsearchPlugin, route
|
|||
});
|
||||
|
||||
route({
|
||||
method: 'DELETE',
|
||||
path: '/api/ml/_data_frame/transforms/{transformId}',
|
||||
method: 'POST',
|
||||
path: '/api/ml/_data_frame/transforms/delete_transforms',
|
||||
handler(request) {
|
||||
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
|
||||
const { transformId } = request.params;
|
||||
return callWithRequest('ml.deleteDataFrameTransform', { transformId })
|
||||
const { deleteTransforms } = transformServiceProvider(callWithRequest);
|
||||
const { transformsInfo } = request.payload;
|
||||
return deleteTransforms(transformsInfo)
|
||||
.catch(resp => wrapError(resp));
|
||||
},
|
||||
config: {
|
||||
|
@ -107,18 +109,12 @@ export function dataFrameRoutes({ commonRouteConfig, elasticsearchPlugin, route
|
|||
|
||||
route({
|
||||
method: 'POST',
|
||||
path: '/api/ml/_data_frame/transforms/{transformId}/_start',
|
||||
path: '/api/ml/_data_frame/transforms/start_transforms',
|
||||
handler(request) {
|
||||
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
|
||||
const options = {
|
||||
transformId: request.params.transformId
|
||||
};
|
||||
|
||||
if (request.query.force !== undefined) {
|
||||
options.force = request.query.force;
|
||||
}
|
||||
|
||||
return callWithRequest('ml.startDataFrameTransform', options)
|
||||
const { startTransforms } = transformServiceProvider(callWithRequest);
|
||||
const { transformsInfo } = request.payload;
|
||||
return startTransforms(transformsInfo)
|
||||
.catch(resp => wrapError(resp));
|
||||
},
|
||||
config: {
|
||||
|
@ -128,22 +124,12 @@ export function dataFrameRoutes({ commonRouteConfig, elasticsearchPlugin, route
|
|||
|
||||
route({
|
||||
method: 'POST',
|
||||
path: '/api/ml/_data_frame/transforms/{transformId}/_stop',
|
||||
path: '/api/ml/_data_frame/transforms/stop_transforms',
|
||||
handler(request) {
|
||||
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
|
||||
const options = {
|
||||
transformId: request.params.transformId
|
||||
};
|
||||
|
||||
if (request.query.force !== undefined) {
|
||||
options.force = request.query.force;
|
||||
}
|
||||
|
||||
if (request.query.wait_for_completion !== undefined) {
|
||||
options.waitForCompletion = request.query.wait_for_completion;
|
||||
}
|
||||
|
||||
return callWithRequest('ml.stopDataFrameTransform', options)
|
||||
const { stopTransforms } = transformServiceProvider(callWithRequest);
|
||||
const { transformsInfo } = request.payload;
|
||||
return stopTransforms(transformsInfo)
|
||||
.catch(resp => wrapError(resp));
|
||||
},
|
||||
config: {
|
||||
|
|
|
@ -5981,7 +5981,6 @@
|
|||
"xpack.ml.dataframe.transformList.dataFrameTitle": "データフレームジョブ",
|
||||
"xpack.ml.dataframe.transformList.deleteActionDisabledToolTipContent": "削除するにはデータフレームジョブを停止してください。",
|
||||
"xpack.ml.dataframe.transformList.deleteActionName": "削除",
|
||||
"xpack.ml.dataframe.transformList.deleteTransformErrorMessage": "データフレームジョブ {transformId} の削除中にエラーが発生しました: {error}",
|
||||
"xpack.ml.dataframe.transformList.deleteTransformSuccessMessage": "データフレームジョブ {transformId} が削除されました",
|
||||
"xpack.ml.dataframe.transformList.deleteModalBody": "このジョブを削除してよろしいですか?ジョブの最大インデックスとオプションの Kibana インデックスパターンは削除されません。",
|
||||
"xpack.ml.dataframe.transformList.deleteModalCancelButton": "キャンセル",
|
||||
|
@ -5991,14 +5990,11 @@
|
|||
"xpack.ml.dataframe.transformList.rowCollapse": "{transformId} の詳細を非表示",
|
||||
"xpack.ml.dataframe.transformList.rowExpand": "{transformId} の詳細を表示",
|
||||
"xpack.ml.dataframe.transformList.startActionName": "開始",
|
||||
"xpack.ml.dataframe.transformList.startTransformErrorMessage": "データフレームジョブ {transformId} の開始中にエラーが発生しました: {error}",
|
||||
"xpack.ml.dataframe.transformList.startTransformSuccessMessage": "データフレームジョブ {transformId} が開始しました",
|
||||
"xpack.ml.dataframe.transformList.startModalBody": "データフレームジョブは、クラスターの検索とインデックスによる負荷を増やします。過剰な負荷が生じた場合はジョブを停止してください。このジョブを開始してよろしいですか?",
|
||||
"xpack.ml.dataframe.transformList.startModalCancelButton": "キャンセル",
|
||||
"xpack.ml.dataframe.transformList.startModalStartButton": "開始",
|
||||
"xpack.ml.dataframe.transformList.startModalTitle": "{transformId} を開始",
|
||||
"xpack.ml.dataframe.transformList.stopActionName": "停止",
|
||||
"xpack.ml.dataframe.transformList.stopTransformErrorMessage": "データフレームジョブ {transformId} の停止中にエラーが発生しました: {error}",
|
||||
"xpack.ml.dataframe.transformList.stopTransformSuccessMessage": "データフレームジョブ {transformId} が停止しました",
|
||||
"xpack.ml.dataframe.noGrantedPrivilegesDescription": "{kibanaUserParam} と {dataFrameUserParam} ロールの権限が必要です。{br}これらのロールはシステム管理者がユーザー管理ページで設定します。",
|
||||
"xpack.ml.dataframe.noPermissionToAccessMLLabel": "データフレームへのアクセスにはパーミッションが必要です",
|
||||
|
|
|
@ -6125,7 +6125,6 @@
|
|||
"xpack.ml.dataframe.transformList.dataFrameTitle": "数据帧作业",
|
||||
"xpack.ml.dataframe.transformList.deleteActionDisabledToolTipContent": "停止数据帧作业,以便将其删除。",
|
||||
"xpack.ml.dataframe.transformList.deleteActionName": "删除",
|
||||
"xpack.ml.dataframe.transformList.deleteTransformErrorMessage": "删除数据帧作业 {transformId} 时发生错误:{error}",
|
||||
"xpack.ml.dataframe.transformList.deleteTransformSuccessMessage": "数据帧作业 {transformId} 删除成功。",
|
||||
"xpack.ml.dataframe.transformList.deleteModalBody": "是否确定要删除此作业?作业的目标索引和可选 Kibana 索引模式将不会删除。",
|
||||
"xpack.ml.dataframe.transformList.deleteModalCancelButton": "取消",
|
||||
|
@ -6135,14 +6134,11 @@
|
|||
"xpack.ml.dataframe.transformList.rowCollapse": "隐藏 {transformId} 的详情",
|
||||
"xpack.ml.dataframe.transformList.rowExpand": "显示 {transformId} 的详情",
|
||||
"xpack.ml.dataframe.transformList.startActionName": "开始",
|
||||
"xpack.ml.dataframe.transformList.startTransformErrorMessage": "启动数据帧作业 {transformId} 时发生错误:{error}",
|
||||
"xpack.ml.dataframe.transformList.startTransformSuccessMessage": "数据帧作业 {transformId} 启动成功。",
|
||||
"xpack.ml.dataframe.transformList.startModalBody": "数据帧作业将增加集群的搜索和索引负荷。如果负荷超载,请停止作业。是否确定要启动此作业?",
|
||||
"xpack.ml.dataframe.transformList.startModalCancelButton": "取消",
|
||||
"xpack.ml.dataframe.transformList.startModalStartButton": "开始",
|
||||
"xpack.ml.dataframe.transformList.startModalTitle": "启动 {transformId}",
|
||||
"xpack.ml.dataframe.transformList.stopActionName": "停止",
|
||||
"xpack.ml.dataframe.transformList.stopTransformErrorMessage": "停止数据帧作业 {transformId} 时发生错误:{error}",
|
||||
"xpack.ml.dataframe.transformList.stopTransformSuccessMessage": "数据帧作业 {transformId} 停止成功。",
|
||||
"xpack.ml.dataframe.noGrantedPrivilegesDescription": "您必须具有 {kibanaUserParam} 和 {dataFrameUserParam} 角色授予的权限。{br}您的系统管理员可以在“管理用户”页面上设置这些角色。",
|
||||
"xpack.ml.dataframe.noPermissionToAccessMLLabel": "您需要访问数据帧的权限",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue