diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx index 45f883c4ccd94..86e2c5fd2fb94 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx @@ -30,7 +30,7 @@ import { DataFrameAnalyticsConfig, } from '../../../../common'; import { isKeywordAndTextType } from '../../../../common/fields'; -import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; +import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns'; import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; import { isResultsSearchBoolQuery, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index 755bac699ce40..8395a11bd6fda 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx @@ -29,7 +29,7 @@ import { SEARCH_SIZE, defaultSearchQuery, } from '../../../../common'; -import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; +import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns'; import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; import { ExplorationTitle } from '../exploration_title'; import { ExplorationQueryBar } from '../exploration_query_bar'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 9afb50c11fad7..9341c0aa1a338 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -29,7 +29,7 @@ import { getToastNotifications } from '../../../../../util/dependency_cache'; import { defaultSearchQuery, useResultsViewConfig, INDEX_STATUS } from '../../../../common'; -import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; +import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns'; import { ExplorationQueryBar } from '../exploration_query_bar'; import { ExplorationTitle } from '../exploration_title'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx index f6e8e0047671f..d31b7734f9969 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx @@ -27,7 +27,7 @@ import { Eval, DataFrameAnalyticsConfig, } from '../../../../common'; -import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; +import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/use_columns'; import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; import { EvaluateStat } from './evaluate_stat'; import { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts similarity index 99% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts index 4227c19fec5af..006cccf3b4610 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isAdvancedConfig } from './action_clone'; +import { isAdvancedConfig } from './clone_button'; describe('Analytics job clone action', () => { describe('isAdvancedConfig', () => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx similarity index 98% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx index bff54bc283296..f8b6fdfbe2119 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx @@ -19,7 +19,7 @@ import { DEFAULT_NUM_TOP_FEATURE_IMPORTANCE_VALUES, } from '../../hooks/use_create_analytics_form'; import { State } from '../../hooks/use_create_analytics_form/state'; -import { DataFrameAnalyticsListRow } from './common'; +import { DataFrameAnalyticsListRow } from '../analytics_list/common'; import { checkPermission } from '../../../../../capabilities/check_capabilities'; import { extractErrorMessage } from '../../../../../../../common/util/errors'; @@ -343,7 +343,7 @@ export function getCloneAction(createAnalyticsForm: CreateAnalyticsFormProps) { }; } -interface CloneActionProps { +interface CloneButtonProps { item: DataFrameAnalyticsListRow; createAnalyticsForm: CreateAnalyticsFormProps; } @@ -353,7 +353,7 @@ interface CloneActionProps { * Replace with {@link getCloneAction} as soon as all the actions are refactored * to support EuiContext with a valid DOM structure without nested buttons. */ -export const CloneAction: FC = ({ createAnalyticsForm, item }) => { +export const CloneButton: FC = ({ createAnalyticsForm, item }) => { const canCreateDataFrameAnalytics: boolean = checkPermission('canCreateDataFrameAnalytics'); const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.cloneJobButtonLabel', { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts new file mode 100644 index 0000000000000..b3d7189ff8cda --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export { + extractCloningConfig, + isAdvancedConfig, + CloneButton, + CloneDataFrameAnalyticsConfig, +} from './clone_button'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/action_delete.test.tsx similarity index 78% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/action_delete.test.tsx index 33217f127f998..8d6272c5df860 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/action_delete.test.tsx @@ -7,14 +7,17 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import * as CheckPrivilige from '../../../../../capabilities/check_capabilities'; -import mockAnalyticsListItem from './__mocks__/analytics_list_item.json'; -import { DeleteAction } from './action_delete'; +import mockAnalyticsListItem from '../analytics_list/__mocks__/analytics_list_item.json'; import { I18nProvider } from '@kbn/i18n/react'; import { coreMock as mockCoreServices, i18nServiceMock, } from '../../../../../../../../../../src/core/public/mocks'; +import { DeleteButton } from './delete_button'; +import { DeleteButtonModal } from './delete_button_modal'; +import { useDeleteAction } from './use_delete_action'; + jest.mock('../../../../../capabilities/check_capabilities', () => ({ checkPermission: jest.fn(() => false), createPermissionFailureMessage: jest.fn(), @@ -41,14 +44,18 @@ describe('DeleteAction', () => { }); test('When canDeleteDataFrameAnalytics permission is false, button should be disabled.', () => { - const { getByTestId } = render(); + const { getByTestId } = render( + {}} /> + ); expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); }); test('When canDeleteDataFrameAnalytics permission is true, button should not be disabled.', () => { const mock = jest.spyOn(CheckPrivilige, 'checkPermission'); mock.mockImplementation((p) => p === 'canDeleteDataFrameAnalytics'); - const { getByTestId } = render(); + const { getByTestId } = render( + {}} /> + ); expect(getByTestId('mlAnalyticsJobDeleteButton')).not.toHaveAttribute('disabled'); @@ -57,11 +64,12 @@ describe('DeleteAction', () => { test('When job is running, delete button should be disabled.', () => { const { getByTestId } = render( - {}} /> ); @@ -72,9 +80,21 @@ describe('DeleteAction', () => { test('should allow to delete target index by default.', () => { const mock = jest.spyOn(CheckPrivilige, 'checkPermission'); mock.mockImplementation((p) => p === 'canDeleteDataFrameAnalytics'); + + const TestComponent = () => { + const deleteAction = useDeleteAction(); + + return ( + <> + {deleteAction.isModalVisible && } + + + ); + }; + const { getByTestId, queryByTestId } = render( - + ); const deleteButton = getByTestId('mlAnalyticsJobDeleteButton'); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button.tsx new file mode 100644 index 0000000000000..7da3bced48576 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; +import { + checkPermission, + createPermissionFailureMessage, +} from '../../../../../capabilities/check_capabilities'; +import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from '../analytics_list/common'; + +interface DeleteButtonProps { + item: DataFrameAnalyticsListRow; + onClick: (item: DataFrameAnalyticsListRow) => void; +} + +export const DeleteButton: FC = ({ item, onClick }) => { + const disabled = isDataFrameAnalyticsRunning(item.stats.state); + const canDeleteDataFrameAnalytics: boolean = checkPermission('canDeleteDataFrameAnalytics'); + + const buttonDeleteText = i18n.translate('xpack.ml.dataframe.analyticsList.deleteActionName', { + defaultMessage: 'Delete', + }); + + const buttonDisabled = disabled || !canDeleteDataFrameAnalytics; + let deleteButton = ( + onClick(item)} + aria-label={buttonDeleteText} + style={{ padding: 0 }} + > + {buttonDeleteText} + + ); + + if (disabled || !canDeleteDataFrameAnalytics) { + deleteButton = ( + + {deleteButton} + + ); + } + + return deleteButton; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button_modal.tsx new file mode 100644 index 0000000000000..f94dccee479bd --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button_modal.tsx @@ -0,0 +1,108 @@ +/* + * 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 { + EuiConfirmModal, + EuiOverlayMask, + EuiSwitch, + EuiFlexGroup, + EuiFlexItem, + EUI_MODAL_CONFIRM_BUTTON, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { DeleteAction } from './use_delete_action'; + +export const DeleteButtonModal: FC = ({ + closeModal, + deleteAndCloseModal, + deleteTargetIndex, + deleteIndexPattern, + indexPatternExists, + item, + toggleDeleteIndex, + toggleDeleteIndexPattern, + userCanDeleteIndex, +}) => { + if (item === undefined) { + return null; + } + + const indexName = item.config.dest.index; + + return ( + + +

+ +

+ + + + {userCanDeleteIndex && ( + + )} + + + {userCanDeleteIndex && indexPatternExists && ( + + )} + + +
+
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts new file mode 100644 index 0000000000000..ef891d7c4a128 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { DeleteButton } from './delete_button'; +export { DeleteButtonModal } from './delete_button_modal'; +export { useDeleteAction } from './use_delete_action'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.ts new file mode 100644 index 0000000000000..f924cf3afcba5 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.ts @@ -0,0 +1,140 @@ +/* + * 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 { useEffect, useState } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { IIndexPattern } from 'src/plugins/data/common'; + +import { extractErrorMessage } from '../../../../../../../common/util/errors'; + +import { useMlKibana } from '../../../../../contexts/kibana'; + +import { + deleteAnalytics, + deleteAnalyticsAndDestIndex, + canDeleteIndex, +} from '../../services/analytics_service'; + +import { DataFrameAnalyticsListRow } from '../analytics_list/common'; + +export type DeleteAction = ReturnType; +export const useDeleteAction = () => { + const [item, setItem] = useState(); + + const [isModalVisible, setModalVisible] = useState(false); + const [deleteTargetIndex, setDeleteTargetIndex] = useState(true); + const [deleteIndexPattern, setDeleteIndexPattern] = useState(true); + const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); + const [indexPatternExists, setIndexPatternExists] = useState(false); + + const { savedObjects, notifications } = useMlKibana().services; + const savedObjectsClient = savedObjects.client; + + const indexName = item?.config.dest.index ?? ''; + + const checkIndexPatternExists = async () => { + try { + const response = await savedObjectsClient.find({ + type: 'index-pattern', + perPage: 10, + search: `"${indexName}"`, + searchFields: ['title'], + fields: ['title'], + }); + const ip = response.savedObjects.find( + (obj) => obj.attributes.title.toLowerCase() === indexName.toLowerCase() + ); + if (ip !== undefined) { + setIndexPatternExists(true); + } + } catch (e) { + const { toasts } = notifications; + const error = extractErrorMessage(e); + + toasts.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfIndexPatternExistsNotificationErrorMessage', + { + defaultMessage: + 'An error occurred checking if index pattern {indexPattern} exists: {error}', + values: { indexPattern: indexName, error }, + } + ) + ); + } + }; + const checkUserIndexPermission = () => { + try { + const userCanDelete = canDeleteIndex(indexName); + if (userCanDelete) { + setUserCanDeleteIndex(true); + } + } catch (e) { + const { toasts } = notifications; + const error = extractErrorMessage(e); + + toasts.addDanger( + i18n.translate( + 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage', + { + defaultMessage: + 'An error occurred checking if user can delete {destinationIndex}: {error}', + values: { destinationIndex: indexName, error }, + } + ) + ); + } + }; + + useEffect(() => { + // Check if an index pattern exists corresponding to current DFA job + // if pattern does exist, show it to user + checkIndexPatternExists(); + + // Check if an user has permission to delete the index & index pattern + checkUserIndexPermission(); + }, []); + + const closeModal = () => setModalVisible(false); + const deleteAndCloseModal = () => { + setModalVisible(false); + + if (item !== undefined) { + if ((userCanDeleteIndex && deleteTargetIndex) || (userCanDeleteIndex && deleteIndexPattern)) { + deleteAnalyticsAndDestIndex( + item, + deleteTargetIndex, + indexPatternExists && deleteIndexPattern + ); + } else { + deleteAnalytics(item); + } + } + }; + const toggleDeleteIndex = () => setDeleteTargetIndex(!deleteTargetIndex); + const toggleDeleteIndexPattern = () => setDeleteIndexPattern(!deleteIndexPattern); + + const openModal = (newItem: DataFrameAnalyticsListRow) => { + setItem(newItem); + setModalVisible(true); + }; + + return { + closeModal, + deleteAndCloseModal, + deleteTargetIndex, + deleteIndexPattern, + indexPatternExists, + isModalVisible, + item, + openModal, + toggleDeleteIndex, + toggleDeleteIndexPattern, + userCanDeleteIndex, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_edit.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button.tsx similarity index 55% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_edit.tsx rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button.tsx index 041b52d0322c4..0acb215336faf 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_edit.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button.tsx @@ -4,44 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, FC } from 'react'; +import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; import { checkPermission } from '../../../../../capabilities/check_capabilities'; -import { DataFrameAnalyticsListRow } from './common'; -import { EditAnalyticsFlyout } from './edit_analytics_flyout'; - -interface EditActionProps { - item: DataFrameAnalyticsListRow; +interface EditButtonProps { + onClick: () => void; } -export const EditAction: FC = ({ item }) => { +export const EditButton: FC = ({ onClick }) => { const canCreateDataFrameAnalytics: boolean = checkPermission('canCreateDataFrameAnalytics'); - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const closeFlyout = () => setIsFlyoutVisible(false); - const showFlyout = () => setIsFlyoutVisible(true); - const buttonEditText = i18n.translate('xpack.ml.dataframe.analyticsList.editActionName', { defaultMessage: 'Edit', }); + const buttonDisabled = !canCreateDataFrameAnalytics; const editButton = ( - - {buttonEditText} - + {buttonEditText} + ); if (!canCreateDataFrameAnalytics) { @@ -57,10 +50,5 @@ export const EditAction: FC = ({ item }) => { ); } - return ( - <> - {editButton} - {isFlyoutVisible && } - - ); + return editButton; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/edit_analytics_flyout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button_flyout.tsx similarity index 97% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/edit_analytics_flyout.tsx rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button_flyout.tsx index b6aed9321e4e3..728f53bf69ee2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/edit_analytics_flyout.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/edit_button_flyout.tsx @@ -32,20 +32,17 @@ import { MemoryInputValidatorResult, } from '../../../../../../../common/util/validators'; import { extractErrorMessage } from '../../../../../../../common/util/errors'; -import { DataFrameAnalyticsListRow, DATA_FRAME_TASK_STATE } from './common'; +import { DATA_FRAME_TASK_STATE } from '../analytics_list/common'; import { useRefreshAnalyticsList, UpdateDataFrameAnalyticsConfig, } from '../../../../common/analytics'; -interface EditAnalyticsJobFlyoutProps { - closeFlyout: () => void; - item: DataFrameAnalyticsListRow; -} +import { EditAction } from './use_edit_action'; let mmLValidator: (value: any) => MemoryInputValidatorResult; -export const EditAnalyticsFlyout: FC = ({ closeFlyout, item }) => { +export const EditButtonFlyout: FC> = ({ closeFlyout, item }) => { const { id: jobId, config } = item; const { state } = item.stats; const initialAllowLazyStart = diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts new file mode 100644 index 0000000000000..cfb0bba16ca18 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { EditButton } from './edit_button'; +export { EditButtonFlyout } from './edit_button_flyout'; +export { isEditActionFlyoutVisible, useEditAction } from './use_edit_action'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.ts new file mode 100644 index 0000000000000..82a7bcc91997a --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.ts @@ -0,0 +1,37 @@ +/* + * 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 { useState } from 'react'; + +import { DataFrameAnalyticsListRow } from '../analytics_list/common'; + +export const isEditActionFlyoutVisible = (editAction: any): editAction is Required => { + return editAction.isFlyoutVisible === true && editAction.item !== undefined; +}; + +export interface EditAction { + isFlyoutVisible: boolean; + item?: DataFrameAnalyticsListRow; + closeFlyout: () => void; + openFlyout: (newItem: DataFrameAnalyticsListRow) => void; +} +export const useEditAction = () => { + const [item, setItem] = useState(); + + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const closeFlyout = () => setIsFlyoutVisible(false); + const openFlyout = (newItem: DataFrameAnalyticsListRow) => { + setItem(newItem); + setIsFlyoutVisible(true); + }; + + return { + isFlyoutVisible, + item, + closeFlyout, + openFlyout, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts new file mode 100644 index 0000000000000..df6bbb7c61908 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { StartButton } from './start_button'; +export { StartButtonModal } from './start_button_modal'; +export { useStartAction } from './use_start_action'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button.tsx new file mode 100644 index 0000000000000..279a335de8f42 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button.tsx @@ -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 React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; + +import { + checkPermission, + createPermissionFailureMessage, +} from '../../../../../capabilities/check_capabilities'; + +import { DataFrameAnalyticsListRow, isCompletedAnalyticsJob } from '../analytics_list/common'; + +interface StartButtonProps { + item: DataFrameAnalyticsListRow; + onClick: (item: DataFrameAnalyticsListRow) => void; +} + +export const StartButton: FC = ({ item, onClick }) => { + const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); + + const buttonStartText = i18n.translate('xpack.ml.dataframe.analyticsList.startActionName', { + defaultMessage: 'Start', + }); + + // Disable start for analytics jobs which have completed. + const completeAnalytics = isCompletedAnalyticsJob(item.stats); + + const disabled = !canStartStopDataFrameAnalytics || completeAnalytics; + + let startButton = ( + onClick(item)} + aria-label={buttonStartText} + data-test-subj="mlAnalyticsJobStartButton" + > + {buttonStartText} + + ); + + if (!canStartStopDataFrameAnalytics || completeAnalytics) { + startButton = ( + + {startButton} + + ); + } + + return startButton; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button_modal.tsx new file mode 100644 index 0000000000000..664dbe5c62b2f --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button_modal.tsx @@ -0,0 +1,51 @@ +/* + * 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 { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; + +import { StartAction } from './use_start_action'; + +export const StartButtonModal: FC = ({ closeModal, item, startAndCloseModal }) => { + return ( + <> + {item !== undefined && ( + + +

+ {i18n.translate('xpack.ml.dataframe.analyticsList.startModalBody', { + defaultMessage: + 'A data frame analytics job will increase search and indexing load in your cluster. Please stop the analytics job if excessive load is experienced. Are you sure you want to start this analytics job?', + })} +

+
+
+ )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts new file mode 100644 index 0000000000000..8eb6b990827ac --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts @@ -0,0 +1,38 @@ +/* + * 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 { useState } from 'react'; + +import { DataFrameAnalyticsListRow } from '../analytics_list/common'; +import { startAnalytics } from '../../services/analytics_service'; + +export type StartAction = ReturnType; +export const useStartAction = () => { + const [isModalVisible, setModalVisible] = useState(false); + + const [item, setItem] = useState(); + + const closeModal = () => setModalVisible(false); + const startAndCloseModal = () => { + if (item !== undefined) { + setModalVisible(false); + startAnalytics(item); + } + }; + + const openModal = (newItem: DataFrameAnalyticsListRow) => { + setItem(newItem); + setModalVisible(true); + }; + + return { + closeModal, + isModalVisible, + item, + openModal, + startAndCloseModal, + }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts new file mode 100644 index 0000000000000..858b6c70501b3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { StopButton } from './stop_button'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx new file mode 100644 index 0000000000000..b8395f2f7c2a0 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx @@ -0,0 +1,57 @@ +/* + * 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 { + checkPermission, + createPermissionFailureMessage, +} from '../../../../../capabilities/check_capabilities'; + +import { stopAnalytics } from '../../services/analytics_service'; + +import { DataFrameAnalyticsListRow } from '../analytics_list/common'; + +const buttonStopText = i18n.translate('xpack.ml.dataframe.analyticsList.stopActionName', { + defaultMessage: 'Stop', +}); + +interface StopButtonProps { + item: DataFrameAnalyticsListRow; +} + +export const StopButton: FC = ({ item }) => { + const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); + + const stopButton = ( + stopAnalytics(item)} + aria-label={buttonStopText} + data-test-subj="mlAnalyticsJobStopButton" + > + {buttonStopText} + + ); + if (!canStartStopDataFrameAnalytics) { + return ( + + {stopButton} + + ); + } + + return stopButton; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx new file mode 100644 index 0000000000000..e31670ea42ceb --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx @@ -0,0 +1,22 @@ +/* + * 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 from 'react'; + +import { EuiTableActionsColumnType } from '@elastic/eui'; + +import { DataFrameAnalyticsListRow } from '../analytics_list/common'; + +import { ViewButton } from './view_button'; + +export const getViewAction = ( + isManagementTable: boolean = false +): EuiTableActionsColumnType['actions'][number] => ({ + isPrimary: true, + render: (item: DataFrameAnalyticsListRow) => ( + + ), +}); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts new file mode 100644 index 0000000000000..5ac12c12071fd --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { getViewAction } from './get_view_action'; +export { ViewButton } from './view_button'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx new file mode 100644 index 0000000000000..17a18c374dfa6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx @@ -0,0 +1,61 @@ +/* + * 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 } from '@elastic/eui'; + +import { + getAnalysisType, + isRegressionAnalysis, + isOutlierAnalysis, + isClassificationAnalysis, +} from '../../../../common/analytics'; +import { useMlKibana } from '../../../../../contexts/kibana'; + +import { getResultsUrl, DataFrameAnalyticsListRow } from '../analytics_list/common'; + +interface ViewButtonProps { + item: DataFrameAnalyticsListRow; + isManagementTable: boolean; +} + +export const ViewButton: FC = ({ item, isManagementTable }) => { + const { + services: { + application: { navigateToUrl, navigateToApp }, + }, + } = useMlKibana(); + + const analysisType = getAnalysisType(item.config.analysis); + const isDisabled = + !isRegressionAnalysis(item.config.analysis) && + !isOutlierAnalysis(item.config.analysis) && + !isClassificationAnalysis(item.config.analysis); + + const url = getResultsUrl(item.id, analysisType); + const navigator = isManagementTable + ? () => navigateToApp('ml', { path: url }) + : () => navigateToUrl(url); + + return ( + + {i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', { + defaultMessage: 'View', + })} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx deleted file mode 100644 index 38ef00914e8fb..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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, { Fragment, FC, useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { - EuiButtonEmpty, - EuiConfirmModal, - EuiOverlayMask, - EuiToolTip, - EuiSwitch, - EuiFlexGroup, - EuiFlexItem, - EUI_MODAL_CONFIRM_BUTTON, -} from '@elastic/eui'; -import { IIndexPattern } from 'src/plugins/data/common'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { extractErrorMessage } from '../../../../../../../common/util/errors'; -import { - deleteAnalytics, - deleteAnalyticsAndDestIndex, - canDeleteIndex, -} from '../../services/analytics_service'; -import { - checkPermission, - createPermissionFailureMessage, -} from '../../../../../capabilities/check_capabilities'; -import { useMlKibana } from '../../../../../contexts/kibana'; -import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; - -interface DeleteActionProps { - item: DataFrameAnalyticsListRow; -} - -export const DeleteAction: FC = ({ item }) => { - const disabled = isDataFrameAnalyticsRunning(item.stats.state); - const canDeleteDataFrameAnalytics: boolean = checkPermission('canDeleteDataFrameAnalytics'); - - const [isModalVisible, setModalVisible] = useState(false); - const [deleteTargetIndex, setDeleteTargetIndex] = useState(true); - const [deleteIndexPattern, setDeleteIndexPattern] = useState(true); - const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); - const [indexPatternExists, setIndexPatternExists] = useState(false); - - const { savedObjects, notifications } = useMlKibana().services; - const savedObjectsClient = savedObjects.client; - - const indexName = item.config.dest.index; - - const checkIndexPatternExists = async () => { - try { - const response = await savedObjectsClient.find({ - type: 'index-pattern', - perPage: 10, - search: `"${indexName}"`, - searchFields: ['title'], - fields: ['title'], - }); - const ip = response.savedObjects.find( - (obj) => obj.attributes.title.toLowerCase() === indexName.toLowerCase() - ); - if (ip !== undefined) { - setIndexPatternExists(true); - } - } catch (e) { - const { toasts } = notifications; - const error = extractErrorMessage(e); - - toasts.addDanger( - i18n.translate( - 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfIndexPatternExistsNotificationErrorMessage', - { - defaultMessage: - 'An error occurred checking if index pattern {indexPattern} exists: {error}', - values: { indexPattern: indexName, error }, - } - ) - ); - } - }; - const checkUserIndexPermission = () => { - try { - const userCanDelete = canDeleteIndex(indexName); - if (userCanDelete) { - setUserCanDeleteIndex(true); - } - } catch (e) { - const { toasts } = notifications; - const error = extractErrorMessage(e); - - toasts.addDanger( - i18n.translate( - 'xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage', - { - defaultMessage: - 'An error occurred checking if user can delete {destinationIndex}: {error}', - values: { destinationIndex: indexName, error }, - } - ) - ); - } - }; - - useEffect(() => { - // Check if an index pattern exists corresponding to current DFA job - // if pattern does exist, show it to user - checkIndexPatternExists(); - - // Check if an user has permission to delete the index & index pattern - checkUserIndexPermission(); - }, []); - - const closeModal = () => setModalVisible(false); - const deleteAndCloseModal = () => { - setModalVisible(false); - - if ((userCanDeleteIndex && deleteTargetIndex) || (userCanDeleteIndex && deleteIndexPattern)) { - deleteAnalyticsAndDestIndex( - item, - deleteTargetIndex, - indexPatternExists && deleteIndexPattern - ); - } else { - deleteAnalytics(item); - } - }; - const openModal = () => setModalVisible(true); - const toggleDeleteIndex = () => setDeleteTargetIndex(!deleteTargetIndex); - const toggleDeleteIndexPattern = () => setDeleteIndexPattern(!deleteIndexPattern); - - const buttonDeleteText = i18n.translate('xpack.ml.dataframe.analyticsList.deleteActionName', { - defaultMessage: 'Delete', - }); - - let deleteButton = ( - - {buttonDeleteText} - - ); - - if (disabled || !canDeleteDataFrameAnalytics) { - deleteButton = ( - - {deleteButton} - - ); - } - - return ( - - {deleteButton} - {isModalVisible && ( - - -

- -

- - - - {userCanDeleteIndex && ( - - )} - - - {userCanDeleteIndex && indexPatternExists && ( - - )} - - -
-
- )} -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx deleted file mode 100644 index 74eb1d0b02782..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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, { Fragment, FC, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { - EuiButtonEmpty, - EuiConfirmModal, - EuiOverlayMask, - EuiToolTip, - EUI_MODAL_CONFIRM_BUTTON, -} from '@elastic/eui'; - -import { startAnalytics } from '../../services/analytics_service'; - -import { - checkPermission, - createPermissionFailureMessage, -} from '../../../../../capabilities/check_capabilities'; - -import { DataFrameAnalyticsListRow, isCompletedAnalyticsJob } from './common'; - -interface StartActionProps { - item: DataFrameAnalyticsListRow; -} - -export const StartAction: FC = ({ item }) => { - const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); - - const [isModalVisible, setModalVisible] = useState(false); - - const closeModal = () => setModalVisible(false); - const startAndCloseModal = () => { - setModalVisible(false); - startAnalytics(item); - }; - const openModal = () => setModalVisible(true); - - const buttonStartText = i18n.translate('xpack.ml.dataframe.analyticsList.startActionName', { - defaultMessage: 'Start', - }); - - // Disable start for analytics jobs which have completed. - const completeAnalytics = isCompletedAnalyticsJob(item.stats); - - let startButton = ( - - {buttonStartText} - - ); - - if (!canStartStopDataFrameAnalytics || completeAnalytics) { - startButton = ( - - {startButton} - - ); - } - - return ( - - {startButton} - {isModalVisible && ( - - -

- {i18n.translate('xpack.ml.dataframe.analyticsList.startModalBody', { - defaultMessage: - 'A data frame analytics job will increase search and indexing load in your cluster. Please stop the analytics job if excessive load is experienced. Are you sure you want to start this analytics job?', - })} -

-
-
- )} -
- ); -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx deleted file mode 100644 index b03a3a4c4edb2..0000000000000 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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 { - checkPermission, - createPermissionFailureMessage, -} from '../../../../../capabilities/check_capabilities'; - -import { - getAnalysisType, - isRegressionAnalysis, - isOutlierAnalysis, - isClassificationAnalysis, -} from '../../../../common/analytics'; -import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -import { useMlKibana } from '../../../../../contexts/kibana'; -import { CloneAction } from './action_clone'; - -import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; -import { stopAnalytics } from '../../services/analytics_service'; - -import { StartAction } from './action_start'; -import { EditAction } from './action_edit'; -import { DeleteAction } from './action_delete'; - -interface Props { - item: DataFrameAnalyticsListRow; - isManagementTable: boolean; -} - -const AnalyticsViewButton: FC = ({ item, isManagementTable }) => { - const { - services: { - application: { navigateToUrl, navigateToApp }, - }, - } = useMlKibana(); - - const analysisType = getAnalysisType(item.config.analysis); - const isDisabled = - !isRegressionAnalysis(item.config.analysis) && - !isOutlierAnalysis(item.config.analysis) && - !isClassificationAnalysis(item.config.analysis); - - const url = getResultsUrl(item.id, analysisType); - const navigator = isManagementTable - ? () => navigateToApp('ml', { path: url }) - : () => navigateToUrl(url); - - return ( - - {i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', { - defaultMessage: 'View', - })} - - ); -}; - -interface Action { - isPrimary?: boolean; - render: (item: DataFrameAnalyticsListRow) => any; -} - -export const getAnalyticsViewAction = (isManagementTable: boolean = false): Action => ({ - isPrimary: true, - render: (item: DataFrameAnalyticsListRow) => ( - - ), -}); - -export const getActions = ( - createAnalyticsForm: CreateAnalyticsFormProps, - isManagementTable: boolean -) => { - const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); - const actions: Action[] = [getAnalyticsViewAction(isManagementTable)]; - - if (isManagementTable === false) { - actions.push( - ...[ - { - render: (item: DataFrameAnalyticsListRow) => { - if (!isDataFrameAnalyticsRunning(item.stats.state)) { - return ; - } - - const buttonStopText = i18n.translate( - 'xpack.ml.dataframe.analyticsList.stopActionName', - { - defaultMessage: 'Stop', - } - ); - - const stopButton = ( - stopAnalytics(item)} - aria-label={buttonStopText} - data-test-subj="mlAnalyticsJobStopButton" - > - {buttonStopText} - - ); - if (!canStartStopDataFrameAnalytics) { - return ( - - {stopButton} - - ); - } - - return stopButton; - }, - }, - { - render: (item: DataFrameAnalyticsListRow) => { - return ; - }, - }, - { - render: (item: DataFrameAnalyticsListRow) => { - return ; - }, - }, - { - render: (item: DataFrameAnalyticsListRow) => { - return ; - }, - }, - ] - ); - } - - return actions; -}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index dac0de4c7a533..405231aef5774 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useState, useEffect } from 'react'; +import React, { FC, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; @@ -25,7 +25,6 @@ import { ANALYSIS_CONFIG_TYPE, } from '../../../../common'; import { checkPermission } from '../../../../../capabilities/check_capabilities'; -import { getTaskStateBadge, getJobTypeBadge } from './columns'; import { DataFrameAnalyticsListColumn, @@ -38,7 +37,7 @@ import { FieldClause, } from './common'; import { getAnalyticsFactory } from '../../services/analytics_service'; -import { getColumns } from './columns'; +import { getTaskStateBadge, getJobTypeBadge, useColumns } from './use_columns'; import { ExpandedRow } from './expanded_row'; import { ProgressBar, @@ -232,6 +231,14 @@ export const DataFrameAnalyticsList: FC = ({ setIsLoading(false); }; + const { columns, modals } = useColumns( + expandedRowItemIds, + setExpandedRowItemIds, + isManagementTable, + isMlEnabledInSpace, + createAnalyticsForm + ); + // Before the analytics have been loaded for the first time, display the loading indicator only. // Otherwise a user would see 'No data frame analytics found' during the initial loading. if (!isInitialized) { @@ -240,7 +247,7 @@ export const DataFrameAnalyticsList: FC = ({ if (typeof errorMessage !== 'undefined') { return ( - + <> = ({ >
{JSON.stringify(errorMessage)}
-
+ ); } if (analytics.length === 0) { return ( - + <> = ({ {isSourceIndexModalVisible === true && ( setIsSourceIndexModalVisible(false)} /> )} - + ); } - const columns = getColumns( - expandedRowItemIds, - setExpandedRowItemIds, - isManagementTable, - isMlEnabledInSpace, - createAnalyticsForm - ); - const sorting = { sort: { field: sortField, @@ -349,26 +348,6 @@ export const DataFrameAnalyticsList: FC = ({ view: getTaskStateBadge(val), })), }, - // For now analytics jobs are batch only - /* - { - type: 'field_value_selection', - field: 'mode', - name: i18n.translate('xpack.ml.dataframe.analyticsList.modeFilter', { - defaultMessage: 'Mode', - }), - multiSelect: false, - options: Object.values(DATA_FRAME_MODE).map(val => ({ - value: val, - name: val, - view: ( - - {val} - - ), - })), - }, - */ ], }; @@ -386,7 +365,8 @@ export const DataFrameAnalyticsList: FC = ({ }; return ( - + <> + {modals} {analyticsStats && ( @@ -435,6 +415,6 @@ export const DataFrameAnalyticsList: FC = ({ {isSourceIndexModalVisible === true && ( setIsSourceIndexModalVisible(false)} /> )} - + ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index 0ee57fe5be141..4d029ff1d9546 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -24,7 +24,7 @@ import { loadEvalData, Eval, } from '../../../../common'; -import { getTaskStateBadge } from './columns'; +import { getTaskStateBadge } from './use_columns'; import { getDataFrameAnalyticsProgressPhase, isCompletedAnalyticsJob } from './common'; import { isRegressionAnalysis, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx new file mode 100644 index 0000000000000..e75d938116991 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiTableActionsColumnType } from '@elastic/eui'; + +import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; +import { CloneButton } from '../action_clone'; +import { useDeleteAction, DeleteButton, DeleteButtonModal } from '../action_delete'; +import { + isEditActionFlyoutVisible, + useEditAction, + EditButton, + EditButtonFlyout, +} from '../action_edit'; +import { useStartAction, StartButton, StartButtonModal } from '../action_start'; +import { StopButton } from '../action_stop'; +import { getViewAction } from '../action_view'; + +import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; + +export const useActions = ( + createAnalyticsForm: CreateAnalyticsFormProps, + isManagementTable: boolean +): { + actions: EuiTableActionsColumnType['actions']; + modals: JSX.Element | null; +} => { + const deleteAction = useDeleteAction(); + const editAction = useEditAction(); + const startAction = useStartAction(); + + let modals: JSX.Element | null = null; + + const actions: EuiTableActionsColumnType['actions'] = [ + getViewAction(isManagementTable), + ]; + + if (isManagementTable === false) { + modals = ( + <> + {startAction.isModalVisible && } + {deleteAction.isModalVisible && } + {isEditActionFlyoutVisible(editAction) && } + + ); + actions.push( + ...[ + { + render: (item: DataFrameAnalyticsListRow) => { + if (!isDataFrameAnalyticsRunning(item.stats.state)) { + return ; + } + return ; + }, + }, + { + render: (item: DataFrameAnalyticsListRow) => { + return editAction.openFlyout(item)} />; + }, + }, + { + render: (item: DataFrameAnalyticsListRow) => { + return ; + }, + }, + { + render: (item: DataFrameAnalyticsListRow) => { + return ; + }, + }, + ] + ); + } + + return { actions, modals }; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx similarity index 93% rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx rename to x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx index a3d2e65386c19..fa88396461cd7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx @@ -33,7 +33,7 @@ import { DataFrameAnalyticsListRow, DataFrameAnalyticsStats, } from './common'; -import { getActions } from './actions'; +import { useActions } from './use_actions'; enum TASK_STATE_COLOR { analyzing = 'primary', @@ -141,14 +141,14 @@ export const getDFAnalyticsJobIdLink = (item: DataFrameAnalyticsListRow) => ( {item.id} ); -export const getColumns = ( +export const useColumns = ( expandedRowItemIds: DataFrameAnalyticsId[], setExpandedRowItemIds: React.Dispatch>, isManagementTable: boolean = false, isMlEnabledInSpace: boolean = true, createAnalyticsForm?: CreateAnalyticsFormProps ) => { - const actions = getActions(createAnalyticsForm!, isManagementTable); + const { actions, modals } = useActions(createAnalyticsForm!, isManagementTable); function toggleDetails(item: DataFrameAnalyticsListRow) { const index = expandedRowItemIds.indexOf(item.config.id); @@ -253,20 +253,6 @@ export const getColumns = ( width: '100px', 'data-test-subj': 'mlAnalyticsTableColumnStatus', }, - // For now there is batch mode only so we hide this column for now. - /* - { - name: i18n.translate('xpack.ml.dataframe.analyticsList.mode', { defaultMessage: 'Mode' }), - sortable: (item: DataFrameAnalyticsListRow) => item.mode, - truncateText: true, - render(item: DataFrameAnalyticsListRow) { - const mode = item.mode; - const color = 'hollow'; - return {mode}; - }, - width: '100px', - }, - */ progressColumn, { name: i18n.translate('xpack.ml.dataframe.analyticsList.tableActionLabel', { @@ -293,5 +279,5 @@ export const getColumns = ( } } - return columns; + return { columns, modals }; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index da6e2e440a26e..cedbe9094cb20 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -13,7 +13,7 @@ import { DataFrameAnalyticsConfig, ANALYSIS_CONFIG_TYPE, } from '../../../../common/analytics'; -import { CloneDataFrameAnalyticsConfig } from '../../components/analytics_list/action_clone'; +import { CloneDataFrameAnalyticsConfig } from '../../components/action_clone'; export enum DEFAULT_MODEL_MEMORY_LIMIT { regression = '100mb', diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index f95d2f572a406..4c312be26613c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -18,10 +18,7 @@ import { DataFrameAnalyticsId, DataFrameAnalyticsConfig, } from '../../../../common'; -import { - extractCloningConfig, - isAdvancedConfig, -} from '../../components/analytics_list/action_clone'; +import { extractCloningConfig, isAdvancedConfig } from '../../components/action_clone'; import { ActionDispatchers, ACTION } from './actions'; import { reducer } from './reducer'; diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx index f2e6ff7885b16..1eeff6287867d 100644 --- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/table.tsx @@ -22,8 +22,8 @@ import { import { getTaskStateBadge, progressColumn, -} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/columns'; -import { getAnalyticsViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; +} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns'; +import { getViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/action_view'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; const MlInMemoryTable = mlInMemoryTableFactory(); @@ -82,7 +82,7 @@ export const AnalyticsTable: FC = ({ items }) => { name: i18n.translate('xpack.ml.overview.analyticsList.tableActionLabel', { defaultMessage: 'Actions', }), - actions: [getAnalyticsViewAction()], + actions: [getViewAction()], width: '100px', }, ]; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_button.tsx similarity index 80% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_button.tsx index aa78dfb4315f9..4686ede7bc2c2 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/clone_button.tsx @@ -7,7 +7,7 @@ import React, { FC, useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; import { createCapabilityFailureMessage, @@ -20,7 +20,7 @@ interface CloneActionProps { itemId: string; } -export const CloneAction: FC = ({ itemId }) => { +export const CloneButton: FC = ({ itemId }) => { const history = useHistory(); const { canCreateTransform } = useContext(AuthorizationContext).capabilities; @@ -34,17 +34,15 @@ export const CloneAction: FC = ({ itemId }) => { } const cloneButton = ( - - {buttonCloneText} - + {buttonCloneText} + ); if (!canCreateTransform) { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts new file mode 100644 index 0000000000000..727cc87c70f2c --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { CloneButton } from './clone_button'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap new file mode 100644 index 0000000000000..3980cc5d5a1ae --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Transform: Transform List Actions Minimal initialization 1`] = ` + + + + + Delete + + +`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.test.tsx similarity index 74% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.test.tsx index fdd0b821f54fd..63f8243b403d3 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.test.tsx @@ -5,10 +5,10 @@ */ import { shallow } from 'enzyme'; -import React from 'react'; +import React, { ComponentProps } from 'react'; import { TransformListRow } from '../../../../common'; -import { DeleteAction } from './action_delete'; +import { DeleteButton } from './delete_button'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; @@ -18,13 +18,13 @@ jest.mock('../../../../../app/app_dependencies'); describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { const item: TransformListRow = transformListRow; - const props = { - disabled: false, + const props: ComponentProps = { + forceDisable: false, items: [item], - deleteTransform(d: TransformListRow) {}, + onClick: () => {}, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.tsx new file mode 100644 index 0000000000000..b81c3ebc34ca0 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.tsx @@ -0,0 +1,77 @@ +/* + * 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, useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; +import { TRANSFORM_STATE } from '../../../../../../common'; +import { + AuthorizationContext, + createCapabilityFailureMessage, +} from '../../../../lib/authorization'; +import { TransformListRow } from '../../../../common'; + +interface DeleteButtonProps { + items: TransformListRow[]; + forceDisable?: boolean; + onClick: (items: TransformListRow[]) => void; +} + +const transformCanNotBeDeleted = (i: TransformListRow) => + ![TRANSFORM_STATE.STOPPED, TRANSFORM_STATE.FAILED].includes(i.stats.state); + +export const DeleteButton: FC = ({ items, forceDisable, onClick }) => { + const isBulkAction = items.length > 1; + + const disabled = items.some(transformCanNotBeDeleted); + const { canDeleteTransform } = useContext(AuthorizationContext).capabilities; + + const buttonDeleteText = i18n.translate('xpack.transform.transformList.deleteActionName', { + defaultMessage: 'Delete', + }); + const bulkDeleteButtonDisabledText = i18n.translate( + 'xpack.transform.transformList.deleteBulkActionDisabledToolTipContent', + { + defaultMessage: 'One or more selected transforms must be stopped in order to be deleted.', + } + ); + const deleteButtonDisabledText = i18n.translate( + 'xpack.transform.transformList.deleteActionDisabledToolTipContent', + { + defaultMessage: 'Stop the transform in order to delete it.', + } + ); + + const buttonDisabled = forceDisable === true || disabled || !canDeleteTransform; + let deleteButton = ( + onClick(items)} + aria-label={buttonDeleteText} + > + {buttonDeleteText} + + ); + + if (disabled || !canDeleteTransform) { + let content; + if (disabled) { + content = isBulkAction ? bulkDeleteButtonDisabledText : deleteButtonDisabledText; + } else { + content = createCapabilityFailureMessage('canDeleteTransform'); + } + + deleteButton = ( + + {deleteButton} + + ); + } + + return deleteButton; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button_modal.tsx similarity index 54% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button_modal.tsx index 79a9e45e317e5..668e535198649 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button_modal.tsx @@ -4,88 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useContext, useMemo, useState } from 'react'; +import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EUI_MODAL_CONFIRM_BUTTON, - EuiButtonEmpty, EuiConfirmModal, EuiFlexGroup, EuiFlexItem, EuiOverlayMask, EuiSpacer, EuiSwitch, - EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { TRANSFORM_STATE } from '../../../../../../common'; -import { useDeleteIndexAndTargetIndex, useDeleteTransforms } from '../../../../hooks'; -import { - AuthorizationContext, - createCapabilityFailureMessage, -} from '../../../../lib/authorization'; -import { TransformListRow } from '../../../../common'; - -interface DeleteActionProps { - items: TransformListRow[]; - forceDisable?: boolean; -} -const transformCanNotBeDeleted = (i: TransformListRow) => - ![TRANSFORM_STATE.STOPPED, TRANSFORM_STATE.FAILED].includes(i.stats.state); +import { DeleteAction } from './use_delete_action'; -export const DeleteAction: FC = ({ items, forceDisable }) => { +export const DeleteButtonModal: FC = ({ + closeModal, + deleteAndCloseModal, + deleteDestIndex, + deleteIndexPattern, + indexPatternExists, + items, + shouldForceDelete, + toggleDeleteIndex, + toggleDeleteIndexPattern, + userCanDeleteIndex, +}) => { const isBulkAction = items.length > 1; - 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 { - userCanDeleteIndex, - deleteDestIndex, - indexPatternExists, - deleteIndexPattern, - toggleDeleteIndex, - toggleDeleteIndexPattern, - } = useDeleteIndexAndTargetIndex(items); - - const [isModalVisible, setModalVisible] = useState(false); - - const closeModal = () => setModalVisible(false); - const deleteAndCloseModal = () => { - setModalVisible(false); - - const shouldDeleteDestIndex = userCanDeleteIndex && deleteDestIndex; - const shouldDeleteDestIndexPattern = - userCanDeleteIndex && indexPatternExists && deleteIndexPattern; - // 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); - - const buttonDeleteText = i18n.translate('xpack.transform.transformList.deleteActionName', { - defaultMessage: 'Delete', - }); - const bulkDeleteButtonDisabledText = i18n.translate( - 'xpack.transform.transformList.deleteBulkActionDisabledToolTipContent', - { - defaultMessage: 'One or more selected transforms must be stopped in order to be deleted.', - } - ); - const deleteButtonDisabledText = i18n.translate( - 'xpack.transform.transformList.deleteActionDisabledToolTipContent', - { - defaultMessage: 'Stop the transform in order to delete it.', - } - ); const bulkDeleteModalTitle = i18n.translate( 'xpack.transform.transformList.bulkDeleteModalTitle', { @@ -203,63 +151,23 @@ export const DeleteAction: FC = ({ items, forceDisable }) => ); - let deleteButton = ( - - {buttonDeleteText} - - ); - - if (disabled || !canDeleteTransform) { - let content; - if (disabled) { - content = isBulkAction ? bulkDeleteButtonDisabledText : deleteButtonDisabledText; - } else { - content = createCapabilityFailureMessage('canDeleteTransform'); - } - - deleteButton = ( - - {deleteButton} - - ); - } - return ( - - {deleteButton} - {isModalVisible && ( - - - {isBulkAction ? bulkDeleteModalContent : deleteModalContent} - - - )} - + + + {isBulkAction ? bulkDeleteModalContent : deleteModalContent} + + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts new file mode 100644 index 0000000000000..ef891d7c4a128 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { DeleteButton } from './delete_button'; +export { DeleteButtonModal } from './delete_button_modal'; +export { useDeleteAction } from './use_delete_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.ts new file mode 100644 index 0000000000000..d76eebe954d7b --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.ts @@ -0,0 +1,75 @@ +/* + * 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 { useMemo, useState } from 'react'; + +import { TRANSFORM_STATE } from '../../../../../../common'; + +import { TransformListRow } from '../../../../common'; +import { useDeleteIndexAndTargetIndex, useDeleteTransforms } from '../../../../hooks'; + +export type DeleteAction = ReturnType; +export const useDeleteAction = () => { + const deleteTransforms = useDeleteTransforms(); + + const [isModalVisible, setModalVisible] = useState(false); + const [items, setItems] = useState([]); + + const isBulkAction = items.length > 1; + const shouldForceDelete = useMemo( + () => items.some((i: TransformListRow) => i.stats.state === TRANSFORM_STATE.FAILED), + [items] + ); + + const closeModal = () => setModalVisible(false); + + const { + userCanDeleteIndex, + deleteDestIndex, + indexPatternExists, + deleteIndexPattern, + toggleDeleteIndex, + toggleDeleteIndexPattern, + } = useDeleteIndexAndTargetIndex(items); + + const deleteAndCloseModal = () => { + setModalVisible(false); + + const shouldDeleteDestIndex = userCanDeleteIndex && deleteDestIndex; + const shouldDeleteDestIndexPattern = + userCanDeleteIndex && indexPatternExists && deleteIndexPattern; + // 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 = (newItems: TransformListRow[]) => { + // EUI issue: Might trigger twice, one time as an array, + // one time as a single object. See https://github.com/elastic/eui/issues/3679 + if (Array.isArray(newItems)) { + setItems(newItems); + setModalVisible(true); + } + }; + + return { + closeModal, + deleteAndCloseModal, + deleteDestIndex, + deleteIndexPattern, + indexPatternExists, + isModalVisible, + items, + openModal, + shouldForceDelete, + toggleDeleteIndex, + toggleDeleteIndexPattern, + userCanDeleteIndex, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_edit.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_button.tsx similarity index 53% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_edit.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_button.tsx index dfb4cd443e904..6ba8e7aeba20f 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_edit.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/edit_button.tsx @@ -4,47 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useState, FC } from 'react'; +import React, { useContext, FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; -import { TransformPivotConfig } from '../../../../common'; import { createCapabilityFailureMessage, AuthorizationContext, } from '../../../../lib/authorization'; -import { EditTransformFlyout } from '../edit_transform_flyout'; - -interface EditActionProps { - config: TransformPivotConfig; +interface EditButtonProps { + onClick: () => void; } - -export const EditAction: FC = ({ config }) => { +export const EditButton: FC = ({ onClick }) => { const { canCreateTransform } = useContext(AuthorizationContext).capabilities; - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const closeFlyout = () => setIsFlyoutVisible(false); - const showFlyout = () => setIsFlyoutVisible(true); - const buttonEditText = i18n.translate('xpack.transform.transformList.editActionName', { defaultMessage: 'Edit', }); const editButton = ( - - {buttonEditText} - + {buttonEditText} + ); if (!canCreateTransform) { @@ -57,10 +47,5 @@ export const EditAction: FC = ({ config }) => { ); } - return ( - <> - {editButton} - {isFlyoutVisible && } - - ); + return editButton; }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts new file mode 100644 index 0000000000000..17a2ad9444f8d --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { EditButton } from './edit_button'; +export { useEditAction } from './use_edit_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts new file mode 100644 index 0000000000000..ace3ec8f636e6 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts @@ -0,0 +1,26 @@ +/* + * 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 { useState } from 'react'; + +import { TransformPivotConfig } from '../../../../common'; + +export const useEditAction = () => { + const [config, setConfig] = useState(); + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const closeFlyout = () => setIsFlyoutVisible(false); + const showFlyout = (newConfig: TransformPivotConfig) => { + setConfig(newConfig); + setIsFlyoutVisible(true); + }; + + return { + config, + closeFlyout, + isFlyoutVisible, + showFlyout, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_button.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_button.test.tsx.snap new file mode 100644 index 0000000000000..231a1f30f2c31 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_button.test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Transform: Transform List Actions Minimal initialization 1`] = ` + + + + + Start + + +`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts new file mode 100644 index 0000000000000..df6bbb7c61908 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { StartButton } from './start_button'; +export { StartButtonModal } from './start_button_modal'; +export { useStartAction } from './use_start_action'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.test.tsx similarity index 74% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.test.tsx index 2de115236c4dc..b88e1257f56ad 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.test.tsx @@ -5,10 +5,10 @@ */ import { shallow } from 'enzyme'; -import React from 'react'; +import React, { ComponentProps } from 'react'; import { TransformListRow } from '../../../../common'; -import { StartAction } from './action_start'; +import { StartButton } from './start_button'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; @@ -18,13 +18,13 @@ jest.mock('../../../../../app/app_dependencies'); describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { const item: TransformListRow = transformListRow; - const props = { - disabled: false, + const props: ComponentProps = { + forceDisable: false, items: [item], - startTransform(d: TransformListRow) {}, + onClick: () => {}, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.tsx new file mode 100644 index 0000000000000..a0fe1bfbb9544 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.tsx @@ -0,0 +1,106 @@ +/* + * 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, useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; + +import { TRANSFORM_STATE } from '../../../../../../common'; + +import { + createCapabilityFailureMessage, + AuthorizationContext, +} from '../../../../lib/authorization'; +import { TransformListRow, isCompletedBatchTransform } from '../../../../common'; + +interface StartButtonProps { + items: TransformListRow[]; + forceDisable?: boolean; + onClick: (items: TransformListRow[]) => void; +} +export const StartButton: FC = ({ items, forceDisable, onClick }) => { + const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; + const isBulkAction = items.length > 1; + + const buttonStartText = i18n.translate('xpack.transform.transformList.startActionName', { + defaultMessage: 'Start', + }); + + // Disable start for batch transforms which have completed. + const completedBatchTransform = items.some((i: TransformListRow) => 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: TransformListRow) => i.stats.state === TRANSFORM_STATE.STARTED + ); + + let startedTransformMessage; + let completedBatchTransformMessage; + + if (isBulkAction === true) { + startedTransformMessage = i18n.translate( + 'xpack.transform.transformList.startedTransformBulkToolTip', + { + defaultMessage: 'One or more transforms are already started.', + } + ); + completedBatchTransformMessage = i18n.translate( + 'xpack.transform.transformList.completeBatchTransformBulkActionToolTip', + { + defaultMessage: + 'One or more transforms are completed batch transforms and cannot be restarted.', + } + ); + } else { + startedTransformMessage = i18n.translate( + 'xpack.transform.transformList.startedTransformToolTip', + { + defaultMessage: '{transformId} is already started.', + values: { transformId: items[0] && items[0].config.id }, + } + ); + completedBatchTransformMessage = i18n.translate( + 'xpack.transform.transformList.completeBatchTransformToolTip', + { + defaultMessage: '{transformId} is a completed batch transform and cannot be restarted.', + values: { transformId: items[0] && items[0].config.id }, + } + ); + } + + const actionIsDisabled = + !canStartStopTransform || completedBatchTransform || startedTransform || items.length === 0; + + let content: string | undefined; + if (actionIsDisabled && items.length > 0) { + if (!canStartStopTransform) { + content = createCapabilityFailureMessage('canStartStopTransform'); + } else if (completedBatchTransform) { + content = completedBatchTransformMessage; + } else if (startedTransform) { + content = startedTransformMessage; + } + } + + const disabled = forceDisable === true || actionIsDisabled; + + const startButton = ( + onClick(items)} + > + {buttonStartText} + + ); + if (disabled && content !== undefined) { + return ( + + {startButton} + + ); + } + return startButton; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button_modal.tsx new file mode 100644 index 0000000000000..2ef0d20c45116 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button_modal.tsx @@ -0,0 +1,55 @@ +/* + * 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 { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; + +import { StartAction } from './use_start_action'; + +export const StartButtonModal: FC = ({ + closeModal, + isModalVisible, + items, + startAndCloseModal, +}) => { + const isBulkAction = items.length > 1; + + const bulkStartModalTitle = i18n.translate('xpack.transform.transformList.bulkStartModalTitle', { + defaultMessage: 'Start {count} {count, plural, one {transform} other {transforms}}?', + values: { count: items && items.length }, + }); + const startModalTitle = i18n.translate('xpack.transform.transformList.startModalTitle', { + defaultMessage: 'Start {transformId}', + values: { transformId: items[0] && items[0].config.id }, + }); + + return ( + + +

+ {i18n.translate('xpack.transform.transformList.startModalBody', { + defaultMessage: + 'A 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 }, + })} +

+
+
+ ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts new file mode 100644 index 0000000000000..32d2dc6dabf86 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts @@ -0,0 +1,42 @@ +/* + * 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 { useState } from 'react'; + +import { TransformListRow } from '../../../../common'; +import { useStartTransforms } from '../../../../hooks'; + +export type StartAction = ReturnType; +export const useStartAction = () => { + const startTransforms = useStartTransforms(); + + const [isModalVisible, setModalVisible] = useState(false); + const [items, setItems] = useState([]); + + const closeModal = () => setModalVisible(false); + + const startAndCloseModal = () => { + setModalVisible(false); + startTransforms(items); + }; + + const openModal = (newItems: TransformListRow[]) => { + // EUI issue: Might trigger twice, one time as an array, + // one time as a single object. See https://github.com/elastic/eui/issues/3679 + if (Array.isArray(newItems)) { + setItems(newItems); + setModalVisible(true); + } + }; + + return { + closeModal, + isModalVisible, + items, + openModal, + startAndCloseModal, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_stop.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_button.test.tsx.snap similarity index 78% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_stop.test.tsx.snap rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_button.test.tsx.snap index 97d393bc8128b..dd81bf34bf582 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_stop.test.tsx.snap +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/__snapshots__/stop_button.test.tsx.snap @@ -6,16 +6,17 @@ exports[`Transform: Transform List Actions Minimal initialization delay="regular" position="top" > - + + Stop - + `; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts new file mode 100644 index 0000000000000..858b6c70501b3 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { StopButton } from './stop_button'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.test.tsx similarity index 76% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.test.tsx index a97097d909848..d9c07a9dccc8f 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.test.tsx @@ -5,10 +5,10 @@ */ import { shallow } from 'enzyme'; -import React from 'react'; +import React, { ComponentProps } from 'react'; import { TransformListRow } from '../../../../common'; -import { StopAction } from './action_stop'; +import { StopButton } from './stop_button'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; @@ -18,13 +18,12 @@ jest.mock('../../../../../app/app_dependencies'); describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { const item: TransformListRow = transformListRow; - const props = { - disabled: false, + const props: ComponentProps = { + forceDisable: false, items: [item], - stopTransform(d: TransformListRow) {}, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.tsx similarity index 85% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.tsx index 3f35bef458951..2c67ea3e83ecc 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_button.tsx @@ -6,7 +6,7 @@ import React, { FC, useContext } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; +import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; import { TRANSFORM_STATE } from '../../../../../../common'; @@ -17,12 +17,11 @@ import { } from '../../../../lib/authorization'; import { useStopTransforms } from '../../../../hooks'; -interface StopActionProps { +interface StopButtonProps { items: TransformListRow[]; forceDisable?: boolean; } - -export const StopAction: FC = ({ items, forceDisable }) => { +export const StopButton: FC = ({ items, forceDisable }) => { const isBulkAction = items.length > 1; const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; const stopTransforms = useStopTransforms(); @@ -57,18 +56,17 @@ export const StopAction: FC = ({ items, forceDisable }) => { stopTransforms(items); }; + const disabled = forceDisable === true || !canStartStopTransform || stoppedTransform === true; + const stopButton = ( - - {buttonStopText} - + {buttonStopText} + ); if (!canStartStopTransform || stoppedTransform) { return ( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_delete.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_delete.test.tsx.snap deleted file mode 100644 index da5ad27c9d6b1..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_delete.test.tsx.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - - - Delete - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_start.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_start.test.tsx.snap deleted file mode 100644 index d534f05d3be96..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_start.test.tsx.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Transform List Actions Minimal initialization 1`] = ` - - - - Start - - - -`; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.tsx deleted file mode 100644 index 9edfe7fab70a0..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.tsx +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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, { Fragment, FC, useContext, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { - EuiButtonEmpty, - EuiConfirmModal, - EuiOverlayMask, - EuiToolTip, - EUI_MODAL_CONFIRM_BUTTON, -} from '@elastic/eui'; - -import { TRANSFORM_STATE } from '../../../../../../common'; - -import { useStartTransforms } from '../../../../hooks'; -import { - createCapabilityFailureMessage, - AuthorizationContext, -} from '../../../../lib/authorization'; -import { TransformListRow, isCompletedBatchTransform } from '../../../../common'; - -interface StartActionProps { - items: TransformListRow[]; - forceDisable?: boolean; -} - -export const StartAction: FC = ({ items, forceDisable }) => { - const isBulkAction = items.length > 1; - const { canStartStopTransform } = useContext(AuthorizationContext).capabilities; - const startTransforms = useStartTransforms(); - - const [isModalVisible, setModalVisible] = useState(false); - - const closeModal = () => setModalVisible(false); - const startAndCloseModal = () => { - setModalVisible(false); - startTransforms(items); - }; - const openModal = () => setModalVisible(true); - - const buttonStartText = i18n.translate('xpack.transform.transformList.startActionName', { - defaultMessage: 'Start', - }); - - // Disable start for batch transforms which have completed. - const completedBatchTransform = items.some((i: TransformListRow) => 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: TransformListRow) => i.stats.state === TRANSFORM_STATE.STARTED - ); - - let startedTransformMessage; - let completedBatchTransformMessage; - - if (isBulkAction === true) { - startedTransformMessage = i18n.translate( - 'xpack.transform.transformList.startedTransformBulkToolTip', - { - defaultMessage: 'One or more transforms are already started.', - } - ); - completedBatchTransformMessage = i18n.translate( - 'xpack.transform.transformList.completeBatchTransformBulkActionToolTip', - { - defaultMessage: - 'One or more transforms are completed batch transforms and cannot be restarted.', - } - ); - } else { - startedTransformMessage = i18n.translate( - 'xpack.transform.transformList.startedTransformToolTip', - { - defaultMessage: '{transformId} is already started.', - values: { transformId: items[0] && items[0].config.id }, - } - ); - completedBatchTransformMessage = i18n.translate( - 'xpack.transform.transformList.completeBatchTransformToolTip', - { - defaultMessage: '{transformId} is a completed batch transform and cannot be restarted.', - values: { transformId: items[0] && items[0].config.id }, - } - ); - } - - const actionIsDisabled = !canStartStopTransform || completedBatchTransform || startedTransform; - - let startButton = ( - - {buttonStartText} - - ); - - if (actionIsDisabled) { - let content; - if (!canStartStopTransform) { - content = createCapabilityFailureMessage('canStartStopTransform'); - } else if (completedBatchTransform) { - content = completedBatchTransformMessage; - } else if (startedTransform) { - content = startedTransformMessage; - } - - startButton = ( - - {startButton} - - ); - } - - const bulkStartModalTitle = i18n.translate('xpack.transform.transformList.bulkStartModalTitle', { - defaultMessage: 'Start {count} {count, plural, one {transform} other {transforms}}?', - values: { count: items && items.length }, - }); - const startModalTitle = i18n.translate('xpack.transform.transformList.startModalTitle', { - defaultMessage: 'Start {transformId}', - values: { transformId: items[0] && items[0].config.id }, - }); - - return ( - - {startButton} - {isModalVisible && ( - - -

- {i18n.translate('xpack.transform.transformList.startModalBody', { - defaultMessage: - 'A 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 }, - })} -

-
-
- )} -
- ); -}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx deleted file mode 100644 index 343b5e4db67e3..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 from 'react'; - -import { TRANSFORM_STATE } from '../../../../../../common'; - -import { TransformListRow } from '../../../../common'; - -import { CloneAction } from './action_clone'; -import { DeleteAction } from './action_delete'; -import { EditAction } from './action_edit'; -import { StartAction } from './action_start'; -import { StopAction } from './action_stop'; - -export const getActions = ({ forceDisable }: { forceDisable: boolean }) => { - return [ - { - render: (item: TransformListRow) => { - if (item.stats.state === TRANSFORM_STATE.STOPPED) { - return ; - } - return ; - }, - }, - { - render: (item: TransformListRow) => { - return ; - }, - }, - { - render: (item: TransformListRow) => { - return ; - }, - }, - { - render: (item: TransformListRow) => { - return ; - }, - }, - ]; -}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx index 5e0363d0a7a15..70b3dc7c2bffe 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { TransformList } from './transform_list'; jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); describe('Transform: Transform List ', () => { test('Minimal initialization', () => { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx index b1eea4a09fca3..9df4113fa9a8b 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx @@ -35,13 +35,12 @@ import { AuthorizationContext } from '../../../../lib/authorization'; import { CreateTransformButton } from '../create_transform_button'; import { RefreshTransformListButton } from '../refresh_transform_list_button'; -import { getTaskStateBadge } from './columns'; -import { DeleteAction } from './action_delete'; -import { StartAction } from './action_start'; -import { StopAction } from './action_stop'; +import { useDeleteAction, DeleteButton, DeleteButtonModal } from '../action_delete'; +import { useStartAction, StartButton, StartButtonModal } from '../action_start'; +import { StopButton } from '../action_stop'; import { ItemIdToExpandedRowMap, Clause, TermClause, FieldClause, Value } from './common'; -import { getColumns } from './columns'; +import { getTaskStateBadge, useColumns } from './use_columns'; import { ExpandedRow } from './expanded_row'; function getItemIdToExpandedRowMap( @@ -90,6 +89,8 @@ export const TransformList: FC = ({ const [transformSelection, setTransformSelection] = useState([]); const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false); + const bulkStartAction = useStartAction(); + const bulkDeleteAction = useDeleteAction(); const [searchError, setSearchError] = useState(undefined); @@ -185,6 +186,12 @@ export const TransformList: FC = ({ setIsLoading(false); }; + const { columns, modals: singleActionModals } = useColumns( + expandedRowItemIds, + setExpandedRowItemIds, + transformSelection + ); + // Before the transforms have been loaded for the first time, display the loading indicator only. // Otherwise a user would see 'No transforms found' during the initial loading. if (!isInitialized) { @@ -231,8 +238,6 @@ export const TransformList: FC = ({ ); } - const columns = getColumns(expandedRowItemIds, setExpandedRowItemIds, transformSelection); - const sorting = { sort: { field: sortField, @@ -252,13 +257,13 @@ export const TransformList: FC = ({ const bulkActionMenuItems = [
- +
,
- +
,
- +
, ]; @@ -375,6 +380,13 @@ export const TransformList: FC = ({ return (
+ {/* Bulk Action Modals */} + {bulkStartAction.isModalVisible && } + {bulkDeleteAction.isModalVisible && } + + {/* Single Action Modals */} + {singleActionModals} + { - test('getActions()', () => { - const actions = getActions({ forceDisable: false }); + test('useActions()', () => { + const { result } = renderHook(() => useActions({ forceDisable: false })); + const actions: ReturnType['actions'] = result.current.actions; expect(actions).toHaveLength(4); expect(typeof actions[0].render).toBe('function'); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx new file mode 100644 index 0000000000000..a6b1aa1a1b5c5 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx @@ -0,0 +1,79 @@ +/* + * 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 from 'react'; + +import { EuiTableComputedColumnType } from '@elastic/eui'; + +import { TRANSFORM_STATE } from '../../../../../../common'; + +import { TransformListRow } from '../../../../common'; + +import { CloneButton } from '../action_clone'; +import { useDeleteAction, DeleteButton, DeleteButtonModal } from '../action_delete'; +import { EditTransformFlyout } from '../edit_transform_flyout'; +import { useEditAction, EditButton } from '../action_edit'; +import { useStartAction, StartButton, StartButtonModal } from '../action_start'; +import { StopButton } from '../action_stop'; + +export const useActions = ({ + forceDisable, +}: { + forceDisable: boolean; +}): { actions: Array>; modals: JSX.Element } => { + const deleteAction = useDeleteAction(); + const editAction = useEditAction(); + const startAction = useStartAction(); + + return { + modals: ( + <> + {startAction.isModalVisible && } + {editAction.config && editAction.isFlyoutVisible && ( + + )} + {deleteAction.isModalVisible && } + + ), + actions: [ + { + render: (item: TransformListRow) => { + if (item.stats.state === TRANSFORM_STATE.STOPPED) { + return ( + + ); + } + return ; + }, + }, + { + render: (item: TransformListRow) => { + return editAction.showFlyout(item.config)} />; + }, + }, + { + render: (item: TransformListRow) => { + return ; + }, + }, + { + render: (item: TransformListRow) => { + return ( + + ); + }, + }, + ], + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx similarity index 67% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx index 3c75c33caf840..94d3e5322a2e8 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx @@ -4,13 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getColumns } from './columns'; +import { renderHook } from '@testing-library/react-hooks'; + +import { useColumns } from './use_columns'; jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); describe('Transform: Job List Columns', () => { - test('getColumns()', () => { - const columns = getColumns([], () => {}, []); + test('useColumns()', () => { + const { result } = renderHook(() => useColumns([], () => {}, [])); + const columns: ReturnType['columns'] = result.current.columns; expect(columns).toHaveLength(7); expect(columns[0].isExpander).toBeTruthy(); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx similarity index 96% rename from x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx rename to x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx index 5ed2566e8a194..d2d8c7084941d 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx @@ -30,7 +30,7 @@ import { TransformStats, TRANSFORM_LIST_COLUMN, } from '../../../../common'; -import { getActions } from './actions'; +import { useActions } from './use_actions'; enum STATE_COLOR { aborting = 'warning', @@ -64,12 +64,12 @@ export const getTaskStateBadge = ( ); }; -export const getColumns = ( +export const useColumns = ( expandedRowItemIds: TransformId[], setExpandedRowItemIds: React.Dispatch>, transformSelection: TransformListRow[] ) => { - const actions = getActions({ forceDisable: transformSelection.length > 0 }); + const { actions, modals } = useActions({ forceDisable: transformSelection.length > 0 }); function toggleDetails(item: TransformListRow) { const index = expandedRowItemIds.indexOf(item.config.id); @@ -223,10 +223,10 @@ export const getColumns = ( }, { name: i18n.translate('xpack.transform.tableActionLabel', { defaultMessage: 'Actions' }), - actions, + actions: actions as EuiTableActionsColumnType['actions'], width: '80px', }, ]; - return columns; + return { columns, modals }; };