From acd5da8b9d3c5ae767256387b0b36f716a55ca27 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Wed, 8 Jul 2020 08:45:20 +0100 Subject: [PATCH 1/5] [Functional test] Add retry for dashboard save (#70950) --- test/functional/page_objects/dashboard_page.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 92482a3779771..7c325ba6d4aec 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -290,14 +290,16 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide dashboardName: string, saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true } ) { - await this.enterDashboardTitleAndClickSave(dashboardName, saveOptions); + await retry.try(async () => { + await this.enterDashboardTitleAndClickSave(dashboardName, saveOptions); - if (saveOptions.needsConfirm) { - await this.clickSave(); - } + if (saveOptions.needsConfirm) { + await this.clickSave(); + } - // Confirm that the Dashboard has actually been saved - await testSubjects.existOrFail('saveDashboardSuccess'); + // Confirm that the Dashboard has actually been saved + await testSubjects.existOrFail('saveDashboardSuccess'); + }); const message = await PageObjects.common.closeToast(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.waitForSaveModalToClose(); From bb96f5dd948ed5ad25924bf0140709eb6dbfae50 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 8 Jul 2020 10:10:32 +0200 Subject: [PATCH 2/5] [ML] Transforms/DFA: Refactor list action buttons so modals won't unmount after button click. (#70555) Related to #70383 and #63455. Refactors the action buttons of the transform and data frame analytics jobs list: Previously custom actions included state and JSX for e.g. confirmation modals. Problem with that: If the actions list popover hides, the modal would unmount too. Since EUI's behaviour will change with the release/merge of #70383, we needed a refactor that solves that issue right now. With this PR, state management for UI behaviour that follows after a button click like the confirmation modals was moved to a custom hook which is part of the outer level of the buttons itself. The modal now also gets mounted on the outer level. This way we won't loose the modals state and DOM rendering when the action button hides. Note that this PR doesn't fix the nested buttons issue (#63455) yet. For that we need EUI issue #70383 to be in Kibana which will arrive with EUI v26.3.0 via #70243. So there will be one follow up to that which will focus on getting rid of the nested button structure. --- .../evaluate_panel.tsx | 2 +- .../exploration_results_table.tsx | 2 +- .../outlier_exploration.tsx | 2 +- .../regression_exploration/evaluate_panel.tsx | 2 +- .../clone_button.test.ts} | 2 +- .../clone_button.tsx} | 6 +- .../components/action_clone/index.ts | 12 + .../action_delete.test.tsx | 32 ++- .../action_delete/delete_button.tsx | 64 +++++ .../action_delete/delete_button_modal.tsx | 108 ++++++++ .../components/action_delete/index.ts | 9 + .../action_delete/use_delete_action.ts | 140 ++++++++++ .../edit_button.tsx} | 40 +-- .../edit_button_flyout.tsx} | 9 +- .../components/action_edit/index.ts | 9 + .../components/action_edit/use_edit_action.ts | 37 +++ .../components/action_start/index.ts | 9 + .../components/action_start/start_button.tsx | 66 +++++ .../action_start/start_button_modal.tsx | 51 ++++ .../action_start/use_start_action.ts | 38 +++ .../components/action_stop/index.ts | 7 + .../components/action_stop/stop_button.tsx | 57 ++++ .../action_view/get_view_action.tsx | 22 ++ .../components/action_view/index.ts | 8 + .../components/action_view/view_button.tsx | 61 +++++ .../analytics_list/action_delete.tsx | 248 ------------------ .../analytics_list/action_start.tsx | 120 --------- .../components/analytics_list/actions.tsx | 157 ----------- .../analytics_list/analytics_list.tsx | 54 ++-- .../analytics_list/expanded_row.tsx | 2 +- .../components/analytics_list/use_actions.tsx | 81 ++++++ .../{columns.tsx => use_columns.tsx} | 22 +- .../hooks/use_create_analytics_form/state.ts | 2 +- .../use_create_analytics_form.ts | 5 +- .../components/analytics_panel/table.tsx | 6 +- .../clone_button.tsx} | 16 +- .../components/action_clone/index.ts | 7 + .../__snapshots__/delete_button.test.tsx.snap | 22 ++ .../delete_button.test.tsx} | 12 +- .../action_delete/delete_button.tsx | 77 ++++++ .../delete_button_modal.tsx} | 154 +++-------- .../components/action_delete/index.ts | 9 + .../action_delete/use_delete_action.ts | 75 ++++++ .../edit_button.tsx} | 37 +-- .../components/action_edit/index.ts | 8 + .../components/action_edit/use_edit_action.ts | 26 ++ .../__snapshots__/start_button.test.tsx.snap | 20 ++ .../components/action_start/index.ts | 9 + .../start_button.test.tsx} | 12 +- .../components/action_start/start_button.tsx | 106 ++++++++ .../action_start/start_button_modal.tsx | 55 ++++ .../action_start/use_start_action.ts | 42 +++ .../__snapshots__/stop_button.test.tsx.snap} | 13 +- .../components/action_stop/index.ts | 7 + .../stop_button.test.tsx} | 11 +- .../stop_button.tsx} | 20 +- .../__snapshots__/action_delete.test.tsx.snap | 23 -- .../__snapshots__/action_start.test.tsx.snap | 23 -- .../transform_list/action_start.tsx | 168 ------------ .../components/transform_list/actions.tsx | 45 ---- .../transform_list/transform_list.test.tsx | 1 + .../transform_list/transform_list.tsx | 32 ++- ...{actions.test.tsx => use_actions.test.tsx} | 10 +- .../components/transform_list/use_actions.tsx | 79 ++++++ ...{columns.test.tsx => use_columns.test.tsx} | 10 +- .../{columns.tsx => use_columns.tsx} | 10 +- 66 files changed, 1523 insertions(+), 1108 deletions(-) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/{analytics_list/action_clone.test.ts => action_clone/clone_button.test.ts} (99%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/{analytics_list/action_clone.tsx => action_clone/clone_button.tsx} (98%) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/index.ts rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/{analytics_list => action_delete}/action_delete.test.tsx (78%) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_button_modal.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/use_delete_action.ts rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/{analytics_list/action_edit.tsx => action_edit/edit_button.tsx} (55%) rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/{analytics_list/edit_analytics_flyout.tsx => action_edit/edit_button_flyout.tsx} (97%) create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_edit/use_edit_action.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_button_modal.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/use_start_action.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_button.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/get_view_action.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/index.ts create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_view/view_button.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx delete mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_actions.tsx rename x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/{columns.tsx => use_columns.tsx} (93%) rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/action_clone.tsx => action_clone/clone_button.tsx} (80%) create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_clone/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/__snapshots__/delete_button.test.tsx.snap rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/action_delete.test.tsx => action_delete/delete_button.test.tsx} (74%) create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_button.tsx rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/action_delete.tsx => action_delete/delete_button_modal.tsx} (54%) create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/use_delete_action.ts rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/action_edit.tsx => action_edit/edit_button.tsx} (53%) create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_edit/use_edit_action.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/__snapshots__/start_button.test.tsx.snap create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/index.ts rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/action_start.test.tsx => action_start/start_button.test.tsx} (74%) create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_button_modal.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.ts rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/__snapshots__/action_stop.test.tsx.snap => action_stop/__snapshots__/stop_button.test.tsx.snap} (78%) create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/index.ts rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/action_stop.test.tsx => action_stop/stop_button.test.tsx} (76%) rename x-pack/plugins/transform/public/app/sections/transform_management/components/{transform_list/action_stop.tsx => action_stop/stop_button.tsx} (85%) delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_delete.test.tsx.snap delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/action_start.test.tsx.snap delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.tsx delete mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx rename x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/{actions.test.tsx => use_actions.test.tsx} (63%) create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx rename x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/{columns.test.tsx => use_columns.test.tsx} (67%) rename x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/{columns.tsx => use_columns.tsx} (96%) 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 }; }; From 0ea7f9ff6e32a1d4b3bd9a008ceb46fa00935060 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 8 Jul 2020 11:48:06 +0300 Subject: [PATCH 3/5] [Functional test] Increase the timeout on opening a saved visualization (#70952) * fixes the flakiness on hybrid visualization test * increase timeout to 20 sec to find and click the hybrid visualization --- test/functional/page_objects/visualize_page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 49133d8b13836..a08598fc42d68 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -257,7 +257,7 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide public async openSavedVisualization(vizName: string) { const dataTestSubj = `visListingTitleLink-${vizName.split(' ').join('-')}`; - await testSubjects.click(dataTestSubj); + await testSubjects.click(dataTestSubj, 20000); await header.waitUntilLoadingHasFinished(); } From a0a3e2f9ab2de8c37b9a014ff9c47aa31e87c6cb Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 8 Jul 2020 11:10:03 +0200 Subject: [PATCH 4/5] fix: remove only consecutive ticks in TSVB (#70981) Co-authored-by: Elastic Machine --- package.json | 2 +- packages/kbn-ui-shared-deps/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index bb28c9e27e9f7..6178bb07067d7 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.10.1", "@babel/register": "^7.10.1", "@elastic/apm-rum": "^5.2.0", - "@elastic/charts": "19.8.0", + "@elastic/charts": "19.8.1", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.9.3", "@elastic/eui": "24.1.0", diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index f4d9beb038966..6ea4a621f92f6 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,7 +9,7 @@ "kbn:watch": "node scripts/build --dev --watch" }, "dependencies": { - "@elastic/charts": "19.8.0", + "@elastic/charts": "19.8.1", "@elastic/eui": "24.1.0", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", diff --git a/yarn.lock b/yarn.lock index ac5f653fdf3d5..acf7c3a1e8754 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2144,10 +2144,10 @@ dependencies: "@elastic/apm-rum-core" "^5.3.0" -"@elastic/charts@19.8.0": - version "19.8.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-19.8.0.tgz#d8439288e2574053ca9e6eee6f3b00bf04917803" - integrity sha512-px0mX0UBtFhbt5O4JAqOZPYC+K9avVmjgKPoIqQBMnnwkKtuKGH1mQ7XZro3E7COJ4WQ5nGxWtC+ewlFQP3zww== +"@elastic/charts@19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-19.8.1.tgz#27653823911c26e4703c73588367473215beaf0f" + integrity sha512-vONCrcZ8bZ+C16+bKLoLyNrMC/b2UvYNoPbYcnB5XYAg5a68finvXEcWD6Y+qa7GLaO2CMe5J9eSjLWXHHDmLg== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" From 949941758f1f2633f36210f8c681c320740c22ef Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 8 Jul 2020 11:26:34 +0200 Subject: [PATCH 5/5] [RUM Dashboard] New rum services api to replace usage of get services API (#70746) --- .../RumDashboard/Charts/PageLoadDistChart.tsx | 2 +- .../PercentileAnnotations.tsx | 2 +- .../PageLoadDistribution/index.tsx | 2 +- .../components/app/RumDashboard/index.tsx | 8 +- .../__snapshots__/queries.test.ts.snap | 55 ++ .../rum_client/get_page_load_distribution.ts | 41 +- .../lib/rum_client/get_pl_dist_breakdown.ts | 10 +- .../server/lib/rum_client/get_rum_services.ts | 48 ++ .../apm/server/lib/rum_client/queries.test.ts | 10 + .../apm/server/routes/create_apm_api.ts | 2 + .../plugins/apm/server/routes/rum_client.ts | 13 + .../apm/typings/elasticsearch/aggregations.ts | 1 + .../es_archiver/rum_8.0.0/data.json.gz | Bin 0 -> 11144 bytes .../es_archiver/rum_8.0.0/mappings.json | 600 ++++++++++++++++++ .../apm_api_integration/trial/tests/index.ts | 1 + .../trial/tests/rum_services.ts | 47 ++ 16 files changed, 821 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts create mode 100644 x-pack/test/apm_api_integration/trial/fixtures/es_archiver/rum_8.0.0/data.json.gz create mode 100644 x-pack/test/apm_api_integration/trial/fixtures/es_archiver/rum_8.0.0/mappings.json create mode 100644 x-pack/test/apm_api_integration/trial/tests/rum_services.ts diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx index e17a8046b5c6a..6c5b539fcecfa 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageLoadDistChart.tsx @@ -119,7 +119,7 @@ export function PageLoadDistChart({ xScaleType={ScaleType.Linear} yScaleType={ScaleType.Linear} data={data?.pageLoadDistribution ?? []} - curve={CurveType.CURVE_NATURAL} + curve={CurveType.CURVE_CATMULL_ROM} /> {breakdowns.map(({ name, type }) => ( ): LineAnnotationDatum[] { return Object.entries(values ?? {}).map((value) => ({ - dataValue: Math.round(value[1] / 1000), + dataValue: value[1], details: `${(+value[0]).toFixed(0)}`, })); } diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index 7d48cee49b104..81503e16f7bcf 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -68,7 +68,7 @@ export const PageLoadDistribution = () => { ); const onPercentileChange = (min: number, max: number) => { - setPercentileRange({ min: min * 1000, max: max * 1000 }); + setPercentileRange({ min, max }); }; return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx index 3ddaa66b8de5e..3380a81c7bfab 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx @@ -46,7 +46,7 @@ export function RumOverview() { (callApmApi) => { if (start && end) { return callApmApi({ - pathname: '/api/apm/services', + pathname: '/api/apm/rum-client/services', params: { query: { start, @@ -68,11 +68,7 @@ export function RumOverview() { {!isRumServiceRoute && ( <> - service.serviceName) ?? [] - } - /> + {' '} diff --git a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap index c006d01637483..602eb88ba8940 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap @@ -70,6 +70,9 @@ Object { "durPercentiles": Object { "percentiles": Object { "field": "transaction.duration.us", + "hdr": Object { + "number_of_significant_value_digits": 3, + }, "percents": Array [ 50, 75, @@ -179,3 +182,55 @@ Object { "index": "myIndex", } `; + +exports[`rum client dashboard queries fetches rum services 1`] = ` +Object { + "body": Object { + "aggs": Object { + "services": Object { + "terms": Object { + "field": "service.name", + "size": 1000, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "processor.event": "transaction", + }, + }, + Object { + "term": Object { + "transaction.type": "page-load", + }, + }, + Object { + "exists": Object { + "field": "transaction.marks.navigationTiming.fetchStart", + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + ], + }, + }, + "size": 0, + }, + "index": "myIndex", +} +`; diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts index 43af18999547d..e847a87264759 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts @@ -12,6 +12,12 @@ import { SetupUIFilters, } from '../helpers/setup_request'; +export const MICRO_TO_SEC = 1000000; + +export function microToSec(val: number) { + return Math.round((val / MICRO_TO_SEC + Number.EPSILON) * 100) / 100; +} + export async function getPageLoadDistribution({ setup, minPercentile, @@ -42,6 +48,9 @@ export async function getPageLoadDistribution({ percentiles: { field: 'transaction.duration.us', percents: [50, 75, 90, 95, 99], + hdr: { + number_of_significant_value_digits: 3, + }, }, }, }, @@ -59,20 +68,29 @@ export async function getPageLoadDistribution({ return null; } - const minDuration = aggregations?.minDuration.value ?? 0; + const { durPercentiles, minDuration } = aggregations ?? {}; - const minPerc = minPercentile ? +minPercentile : minDuration; + const minPerc = minPercentile + ? +minPercentile * MICRO_TO_SEC + : minDuration?.value ?? 0; - const maxPercQuery = aggregations?.durPercentiles.values['99.0'] ?? 10000; + const maxPercQuery = durPercentiles?.values['99.0'] ?? 10000; - const maxPerc = maxPercentile ? +maxPercentile : maxPercQuery; + const maxPerc = maxPercentile ? +maxPercentile * MICRO_TO_SEC : maxPercQuery; const pageDist = await getPercentilesDistribution(setup, minPerc, maxPerc); + + Object.entries(durPercentiles?.values ?? {}).forEach(([key, val]) => { + if (durPercentiles?.values?.[key]) { + durPercentiles.values[key] = microToSec(val as number); + } + }); + return { pageLoadDistribution: pageDist, - percentiles: aggregations?.durPercentiles.values, - minDuration: minPerc, - maxDuration: maxPerc, + percentiles: durPercentiles?.values, + minDuration: microToSec(minPerc), + maxDuration: microToSec(maxPerc), }; } @@ -81,9 +99,9 @@ const getPercentilesDistribution = async ( minDuration: number, maxDuration: number ) => { - const stepValue = (maxDuration - minDuration) / 50; + const stepValue = (maxDuration - minDuration) / 100; const stepValues = []; - for (let i = 1; i < 51; i++) { + for (let i = 1; i < 101; i++) { stepValues.push((stepValue * i + minDuration).toFixed(2)); } @@ -103,6 +121,9 @@ const getPercentilesDistribution = async ( field: 'transaction.duration.us', values: stepValues, keyed: false, + hdr: { + number_of_significant_value_digits: 3, + }, }, }, }, @@ -117,7 +138,7 @@ const getPercentilesDistribution = async ( return pageDist.map(({ key, value }, index: number, arr) => { return { - x: Math.round(key / 1000), + x: microToSec(key), y: index === 0 ? value : value - arr[index - 1].value, }; }); diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts index 5ae6bd1540f7c..ea9d701e64c3d 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts @@ -17,6 +17,7 @@ import { USER_AGENT_NAME, USER_AGENT_OS, } from '../../../common/elasticsearch_fieldnames'; +import { MICRO_TO_SEC, microToSec } from './get_page_load_distribution'; export const getBreakdownField = (breakdown: string) => { switch (breakdown) { @@ -38,7 +39,9 @@ export const getPageLoadDistBreakdown = async ( maxDuration: number, breakdown: string ) => { - const stepValue = (maxDuration - minDuration) / 50; + // convert secs to micros + const stepValue = + (maxDuration * MICRO_TO_SEC - minDuration * MICRO_TO_SEC) / 50; const stepValues = []; for (let i = 1; i < 51; i++) { @@ -67,6 +70,9 @@ export const getPageLoadDistBreakdown = async ( field: 'transaction.duration.us', values: stepValues, keyed: false, + hdr: { + number_of_significant_value_digits: 3, + }, }, }, }, @@ -86,7 +92,7 @@ export const getPageLoadDistBreakdown = async ( name: String(key), data: pageDist.values?.map(({ key: pKey, value }, index: number, arr) => { return { - x: Math.round(pKey / 1000), + x: microToSec(pKey), y: index === 0 ? value : value - arr[index - 1].value, }; }), diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts b/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts new file mode 100644 index 0000000000000..5957a25239307 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts @@ -0,0 +1,48 @@ +/* + * 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 { getRumOverviewProjection } from '../../../common/projections/rum_overview'; +import { mergeProjection } from '../../../common/projections/util/merge_projection'; +import { + Setup, + SetupTimeRange, + SetupUIFilters, +} from '../helpers/setup_request'; + +export async function getRumServices({ + setup, +}: { + setup: Setup & SetupTimeRange & SetupUIFilters; +}) { + const projection = getRumOverviewProjection({ + setup, + }); + + const params = mergeProjection(projection, { + body: { + size: 0, + query: { + bool: projection.body.query.bool, + }, + aggs: { + services: { + terms: { + field: 'service.name', + size: 1000, + }, + }, + }, + }, + }); + + const { client } = setup; + + const response = await client.search(params); + + const result = response.aggregations?.services.buckets ?? []; + + return result.map(({ key }) => key as string); +} diff --git a/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts b/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts index 5f5a48eced746..37432672c5d89 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts @@ -11,6 +11,7 @@ import { import { getClientMetrics } from './get_client_metrics'; import { getPageViewTrends } from './get_page_view_trends'; import { getPageLoadDistribution } from './get_page_load_distribution'; +import { getRumServices } from './get_rum_services'; describe('rum client dashboard queries', () => { let mock: SearchParamsMock; @@ -49,4 +50,13 @@ describe('rum client dashboard queries', () => { ); expect(mock.params).toMatchSnapshot(); }); + + it('fetches rum services', async () => { + mock = await inspectSearchParams((setup) => + getRumServices({ + setup, + }) + ); + expect(mock.params).toMatchSnapshot(); + }); }); diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index c314debcd8049..513c44904683e 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -76,6 +76,7 @@ import { rumPageViewsTrendRoute, rumPageLoadDistributionRoute, rumPageLoadDistBreakdownRoute, + rumServicesRoute, } from './rum_client'; import { observabilityDashboardHasDataRoute, @@ -172,6 +173,7 @@ const createApmApi = () => { .add(rumPageLoadDistributionRoute) .add(rumPageLoadDistBreakdownRoute) .add(rumClientMetricsRoute) + .add(rumServicesRoute) // Observability dashboard .add(observabilityDashboardHasDataRoute) diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts index 75651f646a50d..01e549632a0bc 100644 --- a/x-pack/plugins/apm/server/routes/rum_client.ts +++ b/x-pack/plugins/apm/server/routes/rum_client.ts @@ -12,6 +12,7 @@ import { rangeRt, uiFiltersRt } from './default_api_types'; import { getPageViewTrends } from '../lib/rum_client/get_page_view_trends'; import { getPageLoadDistribution } from '../lib/rum_client/get_page_load_distribution'; import { getPageLoadDistBreakdown } from '../lib/rum_client/get_pl_dist_breakdown'; +import { getRumServices } from '../lib/rum_client/get_rum_services'; export const percentileRangeRt = t.partial({ minPercentile: t.string, @@ -91,3 +92,15 @@ export const rumPageViewsTrendRoute = createRoute(() => ({ return getPageViewTrends({ setup, breakdowns }); }, })); + +export const rumServicesRoute = createRoute(() => ({ + path: '/api/apm/rum-client/services', + params: { + query: t.intersection([uiFiltersRt, rangeRt]), + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + + return getRumServices({ setup }); + }, +})); diff --git a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts index a340aa24aebfb..ac7499c23e926 100644 --- a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts +++ b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts @@ -150,6 +150,7 @@ export interface AggregationOptionsByType { field: string; values: string[]; keyed?: boolean; + hdr?: { number_of_significant_value_digits: number }; }; } diff --git a/x-pack/test/apm_api_integration/trial/fixtures/es_archiver/rum_8.0.0/data.json.gz b/x-pack/test/apm_api_integration/trial/fixtures/es_archiver/rum_8.0.0/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..ad3f2351ed30aff77f40ff7098e37ebd0915a0ef GIT binary patch literal 11144 zcmZ9yRa9JE6Sa%GOK|8$8<#YJ;L9+!KO(aCdhNgy8N0e%|l= z7w4QkFZa#bW7Mwo)SN{ZgMskhgG79sWA8>akovswsG0lEPLoN>|6zST%VTHcWVwcP zL`7q1+5FmpQIMod96Fq&d4KXki%^I-jypzyM1CQt@rlsQVf1QLb>jWRWz__8@L(nV2lylI;$ssJTjM zJzT#%;}*P!ov{Kh=nk)EE^Zt|I+hnD9;TZ8J4EjKt{;;>a66H4PSA64jkK5G_mLEz z-9HorZa9vA8)IAXrP?zT2a#oc`!F^>lT;!g{Al*8$w%P+!ry4LZEbPSb?fl5qOW9T zY4_jkM33uv+6PB&w|BQUb{;la!ffKzyIaohQgKFWg}-s?3aWn`CK-&Ic=mtXcv%;G zT5?sAEN5kzsUzB*LyUZOeLNEA$0;^fu+zC$qo`TBU%6uHpS+?`D7L)k*J-#uIXE!) z)r`4&`7zUQ5`Y||*V{+WH7x39;@c4O*_&6qBi3sHr}`gJcUe}1YDrq{#t16-=l3dx zuU_1>ZP_!DtA37*Pe&(rFD~Y8UKC_>Uj|xTT+g?SmC|BGg^kzL6?*^jr?M0R&O;5c zSPT@t^s8J9Hb<#<|3Q6n4Dj}FLzUIlV<=Fo!N?k^Y^aPQfEl3$y90yQ-Fy+uxGJbf5EbSm6!H@;lj_kBEU6EQZXh>7W29T*w8U+dEQAoop|CHniL z&*ksoh7pUL`(tj=HR(O4su?AhTaBir?W&uQ_N$nsKJBL>_u5q-&(HJDUhtOI0=~ZE z&kT;^AygG6X^kuxJ;CbApEGe%x93vH8fqqXtxWU5Y(%?CgdrTCHVc1-xvC(YPnAy` zzVi@XHl(^*NYlN%^@_L>@<-&}+v_`W@L*%lZ*MW_bs9ffx!`#AI-I!}&;HO_oBow} zBrx##=wvTj)APbHKzmdy;Meui%UT8*_r_S@Z6?tjj;8Ab9-qPY=J?A ziN7#F$9Sz7xM+i2CW4DJJ5O`B&h_l=?NvL6zTkti-jUtWAsC~(dy5Vct?+x^*weKg zhHeZ-?AG<1aHyLIicfT8aB@{W|Fdl-ZSX#sXmAR`uWcNuefGW)Q(m6?Ew9>g`M2tq z823byNX66HSgNtnoL^f{bD`ocTU^hrTUPN()d;3(eaW!?9G7qH@0KEASyoPUM;njv z;)lGPoIvq!FP9Jb&!YlqHbPJICih;-bYF{doU)xfoE^!UmZUrquM5sSgNFwy&^PVy z+qP!1>h!~F!8u9)f%tka<{Wwi6 z7{m4NCQRMbq7Q40RoyMV-<#ifdODs~{|RgNS-2n_)7@}4@OK;ayT@~_t;(GFRoPZw zIAcO2wSM`eqevX2)EV2%-cLZVW~AYzk1JR*C!QK`}Je#N|7~zhP(b>9c^NP z!rqq$JBAEH0>`SCX{T>4Z(T0zPCgo{W_>>oMzA9!m^i5Y>XkA_tDz7hgbA3EaZH%wF;Aw6iZ-_s-wDz#5GqHT^ z8GbZ6?sVX&_ik(4Shp5UvO{|wv+yXXdiJI78C_JKSlSWKvM z^lwt`Vd&kqEkaUxVUVCobSRe82F!;>Lctjb87j>c5n@3@g5nz6pwwgmg7L%Pbu_>& z{hK8)(DJ&?fTG6p{72$)KJ_^lwOFDq^{LVYp{(;dgS({1Apl?o>~Bc1hz) z-)@Ndj?ZNRHLxP2iCBC-?unAtUW_u6I?!oP#^iSOdCjCPD|F1#S1ZmJQ zB)qEfTh50u?v3{J6u15*#wXK@-&sQ&ED|b+D*NvU6p%FE@z+D{7UtNy+>+PP|Kg(j z&|Qo}61Wi!yITH$4--)2Pud(?Hs)v&nS3U=6R*NU4#ojGR}$EeqlZeos`D4YU?E|v zv}p>PF-CM`FAO4VR>_)z@zub#i#LK`F_&HeQL3&i@|~c@GujU<<~a!hkR{8q^Au)q z2ruBuyt5_Q)>4A2mK?rek=sBHkM4PEY86zDqo>cPsBk1D5wAh75S1aJ$M`qfBxR%K zbdtz+?RT4ARg0XE5v!N*)YeF!*ZE8@*Lt9ub@Bx&IpY?*g0SXd7a%G%O(hlouF1b` z-NeL$Xs!S2gWKAN6W6t)Lx1NS@b4<3r(I>*^`Z(%%u;S5e!FtkwbWB05R!a{zSLt6 z;nySylrK^ATcWnzhb?4YmLj&`5z{saYzr$%tvVw%ZjTmo^PO%A(%3j!imI>YB&03M zdoxme*2u*9AP_5>l*kh6rS?~Bd_9(B>@&;oeii`wDdZVo^(9ISyfUQXt=gSP5>>n; zZ1h+Wp?T}*#Urv1WwG7T61<9ec+e5dKQ|X}w|FS+mD>WQ>oSD&xe9o;S$b)rFvMim_1>~hR@P#+j)#)l zy$~~n4~WA>x77=} z*TUvWP;$y@4-gPfIL1Hv-q4hT;7~tDmYh}07c1uncsCnSF-k=@zQl-prz^Zgsawh3&7?(` z*gC{II75Pi3JpV><#4do!$&3xX78imL|8S`YTr!7SputkMr13uinaDsA78p>*Fn^x zmSM>yYDF#`Ek{h{dsJLPo9w9wP3=N}Vc40*1O*y7cqB@Um* z+f`v)K4Ypht9#b0d>Ji$F~SPtCX2e!c`)@^({dk9LozeLEd_mmlp1)7uMGe`9(yOp z@7xd^Y+ZVGeJn^{!F6}}`$zJ2Z>ljZVoN<*L`a2JI}{ZCz<2?=uyLX-QtCVrQP9sz z-<1HiyWS0EUlK8TMHQBE=St9i-&0riw#Q}t3jneDx$}~bp-;1Dmx!^BO@T2R4wKoW zzojr^<160)90V|?Lk^Q)KV=)}KS;0f4335}y-5^P=Ywpd`#<#iJ=(pnlGsA)JDnyZy)iv>ctoLw;HSW*GG=UN%Q3%g zqjDTYG|*dEy2g5mWo8f#9w{w;ZQ;So=Fs^yv>{JZ=yC*5zY(AH@C>+ntWQ#iOh2Z{ z3YpW3jQ+~6x>)JOTiHCL3*TQ+CJ8g~F;?U9o7yNTiaiW=weag-&Z2lD0r_15tR2`q)A9DcZ* z-2V1AHlM6K;Yz^NuUD@~Ja~G;@(2-DdK0I8%+7_M1kL*y;#^4+s^JzE8yOo(6Y4LF zK|1#yuOwST=KYM3oMUPO8!R^w%gKC8KbR&IH`FHGkqL6re5N2+)Ks&Kr^Q(ALH5t4 zMm!|LBvj9fO*yo*77M~ZL|f<}#fZ+*JG`ldQC$PkLEj2OdrQtENX`;3Z0bxBb(zlE zMAeniV~J(Q7tnsXo8}GB{dHa)UAAO!ruiSm8|$n5Uvx*8Ztj&KQO8q1kuNYhjB8>T zD%yCzUP$k=Z>wfdV_GM`;i?TKrq&LN`wAT5x6}>-elN;Qn#91Ym<)en0f+eI0obkUpZ zi??YDdrmA)COLlgjQ7makF5!R8vVNdgO&QDTjD{w=Px7PH6)XIKESVbVoO-lO=*i> zw{z^$Co@^x>~VEX-fd4V=Y>RO=PM>0=Hyrbze)au0%7K7A{z8q0Z%C6UzjDw`A|Bb zMZpqSrromkp;}=;Qykn)T(!C|+fIlqpxvc@xm!>!ETM}uEGi)Pi&(jk@EnLnQQPKt z2%KkrU1y+Q{l9W^+)qXy=sQ5r{#@NMSb)~QC&jvX2BybzQn?AZ)2qFqiLNw6T6yHL z*_Mzb=Ek?=7*3=dP>3T5!B3=2q(sk^b#(2ODaQpJGx9R}pDUq#jEhU$-?R_{a1c9y zVP^ZZpYr-^45V1oWck+$`6ly5A=-@{n5TuZf_R@QMYBG#kbm7n>A93USo}%YbmB>@ z?f;XorHSw>^lxbYWRzS~RK?Z9&EDOY%0^O~~ro{O=12v@T_GG6Io(%-4US8mae&%&NDLUxx8r;Bm=f^3o zeUa9@Oj)dWYQL=M;?f;dljkSYGh3$SQdp)&e4Oj5=T=MOhx!S1!76LX?gW_m1K?Nw z>e37;i!yBc#_5OWrmhr8Xm2GwpXsvPnH1TkEhvPiX?C#c`X5{2JBRiZ4J6KK+z}7J zGFhL*-Ln3@zP$Zs30>-GyrdodlQOV*;dnkYM--aTo??j(WAsOucE2d_9-P)Y2a~7- zW9=~H@v9zc|1}~WT2&2 z0{mWv^(-nY`&UhXX8?bP->tcZ3axb`X_@VbIBEc!TeC{-m29V(zJxcOgOkOJ=BB1T z_vsqPoYB`7lU8gz0sKaBn*N^2yzcP+YBs+ld96D*p8D3|_cPXUM|G<-OUS7Q!mcrR zI0XI%>m(FE-dAU^lMs_?Rbz%@UN%%n7S8v1IHYL4zrDHW;)&3VJB)jTlfNQWeFtB| z@uW{$(GTAtZ8$<&D&^9j&2bWLuVosuKg-&fiJKu!Gfik8QD)N5qA2F+y>} z3Su$mKl+w~$iDENGy}uf1|=Fw>S|p{a$DWqL#*iDV6CKfFW1`EiH21kOfC1d%G#~m zq|h00R^qA6-K5;G0a5?fh%%`bMQT|P?lW8`FCmh?1^3)UYX}y3^+~1Il-bT=+wjoES##=a`#i#IvtTMo)e751rU&v}Cq7>vGHU*$4*Qbn4q9yA~2 zc>tPJ={d0}sS!dUop`V*Af&8(c+Y)bO*m&@?#G#7!9G%$-Jf>(z6eI3{Q*@KK= zlh2eypG410eIqZpW6PHU2mb4mLY2w@F6%q@g8+QJB1yLrR$EaRb<;9299P zv9Yer-JjzCMT|sBsH$_I_kEU(C8K6DIcr3gETk5PJYO*5)6eu3qRhT6RM%AA|AE0e z?VWs;l?YCXu;*dyoD3&ny|C4^Txf4%hbqu3$<6|9Q?LspW!WpQbw)s##3BSNNC_B8 z*8o=jf_zT9Nz{*!lq5c)BZn^K4J?gied;C7w->)l0Ywy~h5EtQFf07&H*yC(Wbo;6 zB#j5SdI$yYH6|@>jgNc#z1$BT9ds8KpGvAnexZ54tGYG0MhTai~asLYa@jc{$UlltS&JrDXKr+RfOk^v5GcI?fy2 zFol(KxY&qjs%(b3o-BUk0-f*PU-fH$J4X6s*p;4QYs&EZs%}(+pWomBHsY>!XYkqX6 z7I;*@C*?v?c!Hb(3@nn({FE=xte3TDgW#7(S0kZG z1wDN4z;6FUH(9|db^13?m$_I2lt4dCKYb%>kn^0DI4K!JUWZhLwTI1)K`6^e#11tR z1%*LZg6hJtr|dd2gXSp@VQfzPnm9YtGxBu`alJv>)(FKK`dp&-A$6$f2WQNpIYDK< zh|E;ufuyZGgmej&nK#!*79v?~qridC&w)988z(b?Wy2>U*IWx@Y2yE`Qh`+_N&$~Q z-O&HUL5F8(9g}GH`M`;_WmEpkJahh8Jab$_@&DuXjvFs*`doy! zf!Tof^zY5h940pLevpnhduj6-f8ZiQ+t*Oo@L}W)iqxVkaNpu^wGb6xe)CCN${1l2 zxYiCJL8S=al3mY`?;3(Gb2TsJP`%%AW{5bF6sl=ZbZlv=uIbHfrdjlia!AvYi}9#K zo|}5!GoTr5ah5=zfg=l}KzB-EJ-9hO%9T;U*9C5f0R$>$1(v&dP zx;LaJTuH7+i;5CS7a<(t^c`z_(QH!9LnDEap-f+!qKhtRC)aFknkFP=#r1yjQ>jph zI0n*wLIb2B@~j>-&kgLR{I!y z-_dLRyddFF0;B##OZu5bL}$QR%_=d^3}2_vT!u}j@T8gkx^=`M^Pkzx!8hMr`TRuY z0p(=Ba>R59CXp?XZH>4x(RWwIK~oSerbPXpUevC#v!c@qtMLp|`e{pIWh8;Zv70&B zA6O*}pz^Svet=BIoG--^EpC#3)xh%0wn-u*Vi~fTjQT@>`WP?V*#;ua9I!m?qF9NL zgmxxleh#yI)w68$Qyeszp>g2;{J>afem+KEEUr2|a5W0}f_G+q5u>O(a~(581Y)79 z7fJosXL-Jb4ITMsD-%A!ikKA8W!Zkew;N$>ussI+eluw--m+}z~Kb zoPneQ*k-$4@e?%^ILdRyY-zo*dJYOcKGN}GiyI5m7XmE2j$IWL%{ES52~W~< zOUTk7vCQ+?M_=Lm>=j0h4oV3`2exC|K`9Dtc)HlJ@ zGFzpSt-tdJFY*cDq6#ZbgZL(3HlZ<_(36lD1e&%c|3m-}Jx>Gj@6+Ry!9&{Bj+Gd% z%b#+}PGOO)iay+An5{YrCO&JLuU>@4db~`oK-jmRm1Rm!&9>~fpnpbg4nj#Q(xoMx zE2vcZX9>9OZ(xqp?t!#^#-JiX>>M8SsZ$5lyW$LH~~n!)>p@3L4s!+|Pce z#Z&Mem?#Ke#TcsOc*JT)-|6btk?Qs~RA4AEP%oKL9+bEY=Hm=10~%m%35wX*P5iO( z!DM~!#|=EhX)NVRLJv2-j{^LBCO#A&;AA-oil*=^`hR(umqSb6KMEqcVmT|E+C}hK zY0fMQ=ncDE;b%BEcRSWKRiCmlGM38qWff&=_gpUl4+Fa%sprG|NYt&L3fiy#i`Y0JmyxIp9mlly=amgf;9GB z`~M_V-lC!Qe+pLrkGAbH*0t`tN)r{Zf@ULU1=;M3MB7w#+^cAbfNb4CMvlh)4SL4hMcP|}N-8^4`g!k3Y zp85bc`EnVO3G^|vsIPd!=>tYDkz0~aNTQX;p_er_UE9aVvEnONTPc!xiR(3?a?fxQkP(u7s~KGMVK|A5%!V>~$w7D$Kij>(oTb>y z-Ydr#CDCG2E#!@i#66D6JkWVdby`R~tuRtXIXWuqnYo0~-XYi<5SecFk)Z}l-Rtkx zCxP0$2zwaz~3aksrIe5zNHUwcQ}fV33Hv2m$d|# zq7+QA=ZY9`1)(esf~Abw4B>1B-syKfC{-4!x24eIQqe;saYa%&gaW{wX%wY;EgJ}iQScp)or=el@KaupN%us>id zl%Gyi4AF%~@!L#lI>Jd+?cIw1m9%zw#G&zzz8<=OhhexbH*<&oqP}EXm-N>gXN$-d zLcR?-S*=@A!Z|Lq($|}t?#;`^0ng9Krzi&8-bo`^H%t=(Ng;Sth@5pU@Ir^kNykE# zQ9?%Tb4Q6-Frt=yos@fvdy9)9bF|+;)2l}oUHs@Hq+1Eqtkr3-&uz+4rg%R9@ulo8 ztE44J>?+@Wz#{L-BZdaj_d_;;$&nEZeLbOUqX1g^SKWN)NzENP`ApvQHX40oL@}C& zh@2_Wz=~X#gWwbNQ@`|jd_=z58?35G&(QPsU8bZ&!LJ|tn36a|7+;%sOawkB;3h$F zHEWkOoJoa;<2*5p>6eionYi-N_7l3tJJt1+HjmMt#9!5n5KW!F(*F7*>`%jK(74 zZ0uP106qkZN#D9^O*kgAA+}`dxlj`*!vK%yJe%@vP*Z|I0glPg{x`ySkM2JjA7%;l%_%M|cQuA>aT9e5l@!rFin-)g0% z!Veqb#~HG5@LEOg;H(_s@HHv9E-b zCA~3UwzF9XVbU;w#I2P5oe=y2yv$km7i+)S^=t4hLIA)3oD)U59NTN!Z^@`*le{Zo z_nJN!s)@AqC|Nm|;}u>2u^|-EQ6T1ck!6Bo>wY`&OB1?qruSb;oZUxa8%ddSF2NuH16u`)U-?|;43ugT|G3aK*= z#frfHF`3g)NEyP;)3hChu6q;|V!IvF_wV^DT|eb1r6`dfE;@=X1llr4!HNUwry$ir5Ug3aH!+&@_W(n>^axUq&eT zne#0~Xm`fa81g1+?yKEz48ne9zs-5ppBNUn%(sNSHO7wvgs&A1CJA)mWLr zks#i$s1~c1uk&B*eou^hOBK3JMmpM6O#{}R$P%HXv#|HAP3oI_+x6_q{ ztYR2SBuXUA=z}Dea-_4pZjB<`57ucKbM-Qw2=z}%3b_iKd`C&82Ddkt=z?ScN~Xtb z%kaLq6s)O9UI^PLyK~^!@S!ww=#op;qO&$m;N#Jun8icjR9-5Z>i6BT!YpTt5C7D@ zuLLg#1bPtIAuIWlR~WE>_7*iFD#PuJRqMHwVcC`tQJX{kmi#skz<2CQo@l!={}!Q2 z56UOuECrU}$#*X*W1mpir8c`#U&Qhuw+yT~BI^(@|GViSWR{i!qX<`HflO523|h2f zz5au5i%~%zZ~VTU(X>Pad= zdlcfFd-1L6YC=$%Vj&$f6c0J!9N$z1X$vi8PTflaO2!h^wwnBf_#fQyP@`m!ruJp- zIUn&56Xr!HW3J275$^7Y5jv`7eSWPgelL;LqAs%ol_7?dMwUjFo2kyhZGhJ&ejx0< z9E>D|UGm>nMs0OrL_ao@uqYGpsSbp3>ng(8uq+hcs>tSS{fX3)jxINAwX>mZw_DWS zkXPr2%$=!&@2z+?|36{v+MCy~7CWHQ?<9m|f9qS1|H{DO@vb&Yt;127cuhv@;El#{ z&zG>g4}boyT$1n`m=CdZQ#I^e-;P)s{EeOTMC-(v$?fts){`l)i2sa-Sowu;<&x6v zF4ofFUvZ(iiHo)bn8WN^;=#lk4r`$rG)~!R2+z5qy%%b?<8oV_mS!~Dy`U=tYkg(; z&-8uG`n(ATT`;04hHz8WKcV)&?t1)64g>Do==4!6zTFv-X&Vd)k$)%BpqmXWnH^}A^$N`;&X=(0UGoxI*lrp;bdE^Hn|)UIm@gZyZdxgg71GYo zE@AV&0nixclpj=TYSVa>qxeJR=gt>pzhBKJ;@{$!}aVW`%Kq9h4 z->#ZW}iZacO0eTM$e8WKxm76Wrc;443a9AYBZ+GDPQV^k^%G=aSb^j-vs z4fOecB_Q#hr8W^l1}gu55{;FbeI&`{+VC-1J#jD&dH7$H6a%lR{A0P#>0N$r$v*$v zB(=nAV}Q?2_4*H$5`(lOty5t5ziS>Gd0s=8!nqQxQk})B$iIw>aah7v89$Xj70EdW5>>@rc^lC8oNL1<%PgL7P3a$E7<@E0b!Lln{>)&$rtw6UAF)j&)7~aoRBk?kHJAj+x@!)yy;x zE5?YxC8X3KpF)ZVixykU4JV|7#qq#Xm04~g9re|z!zw|*o?&K1Z4!=Gj98q+p=X;G zQ+YSJZ;9;BD6=FPj?_iab;g_a^&^6r?>=kxbSopq>$=K!fX(p74)H~&=5Enp6=H+-@bSUFPTV=k~TCQ8A& zjxh{Xz8z^RY=j#ctENp=z*n2|I1n$o^C@`*cr)6}6Bt zp)gT311~am0tcG~`a7jdGmbxWUX9=H`-<52ANnly?OJZCy#Ke5@#ThJ@y=HT6Mluw zd^je~Eq+AwXA5J!V@*sDE9Ca$HvZ8dmG z3aT`K<>U>D+$xOKN`as|;ONHkSk|FgtLNDo8s_3nHJfV9PBEhVNrz0aL`hlkW*($L zTySDHH6x`u`&f;5bX+VBs=|sIM*Ut z?y={UqZjbBZ;t=7zk10r#TMUAAu0uJP6z^4@+Idw298DWu@Ft#M}}>~^w>QlErFGF zTm(){#j(Ve_2JtJV+M~Wpg8a{Lb!$l4G_6V)O{ZJ!f{K52DK~-NA=BDrd5{=QRqr8 u8amktq=Y4A)1OD#!!*<9Uv~~<>trZZ%Y3@WSswp|ZiLP8hSm-l;r{^9yF5?; literal 0 HcmV?d00001 diff --git a/x-pack/test/apm_api_integration/trial/fixtures/es_archiver/rum_8.0.0/mappings.json b/x-pack/test/apm_api_integration/trial/fixtures/es_archiver/rum_8.0.0/mappings.json new file mode 100644 index 0000000000000..48ac74d97dfa7 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/fixtures/es_archiver/rum_8.0.0/mappings.json @@ -0,0 +1,600 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "apm-8.0.0-transaction-005", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "client": { + "properties": { + "geo": { + "properties": { + "continent_name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "country_iso_code": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "location": { + "properties": { + "lat": { + "type": "float" + }, + "lon": { + "type": "float" + } + } + } + } + }, + "ip": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ecs": { + "properties": { + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "event": { + "properties": { + "ingested": { + "type": "date" + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "referrer": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "response": { + "properties": { + "decoded_body_size": { + "type": "long" + }, + "encoded_body_size": { + "type": "long" + }, + "transfer_size": { + "type": "long" + } + } + } + } + }, + "observer": { + "properties": { + "ephemeral_id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "hostname": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "type": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "version_major": { + "type": "long" + } + } + }, + "processor": { + "properties": { + "event": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "service": { + "properties": { + "language": { + "properties": { + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "source": { + "properties": { + "ip": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "trace": { + "properties": { + "id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "transaction": { + "properties": { + "custom": { + "properties": { + "userConfig": { + "properties": { + "featureFlags": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "showDashboard": { + "type": "boolean" + } + } + } + } + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "marks": { + "properties": { + "agent": { + "properties": { + "domComplete": { + "type": "long" + }, + "domInteractive": { + "type": "long" + }, + "firstContentfulPaint": { + "type": "float" + }, + "largestContentfulPaint": { + "type": "float" + }, + "timeToFirstByte": { + "type": "long" + } + } + }, + "navigationTiming": { + "properties": { + "connectEnd": { + "type": "long" + }, + "connectStart": { + "type": "long" + }, + "domComplete": { + "type": "long" + }, + "domContentLoadedEventEnd": { + "type": "long" + }, + "domContentLoadedEventStart": { + "type": "long" + }, + "domInteractive": { + "type": "long" + }, + "domLoading": { + "type": "long" + }, + "domainLookupEnd": { + "type": "long" + }, + "domainLookupStart": { + "type": "long" + }, + "fetchStart": { + "type": "long" + }, + "loadEventEnd": { + "type": "long" + }, + "loadEventStart": { + "type": "long" + }, + "requestStart": { + "type": "long" + }, + "responseEnd": { + "type": "long" + }, + "responseStart": { + "type": "long" + } + } + } + } + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "page": { + "properties": { + "referer": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "sampled": { + "type": "boolean" + }, + "span_count": { + "properties": { + "started": { + "type": "long" + } + } + }, + "type": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "url": { + "properties": { + "domain": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "full": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "original": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "path": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "port": { + "type": "long" + }, + "scheme": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "user": { + "properties": { + "email": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "original": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "os": { + "properties": { + "full": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "version": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts index 1a00f7e2df9e8..37328badcb794 100644 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -11,5 +11,6 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr this.tags('ciGroup1'); loadTestFile(require.resolve('./annotations')); loadTestFile(require.resolve('./service_maps')); + loadTestFile(require.resolve('./rum_services')); }); } diff --git a/x-pack/test/apm_api_integration/trial/tests/rum_services.ts b/x-pack/test/apm_api_integration/trial/tests/rum_services.ts new file mode 100644 index 0000000000000..5505387de54a7 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/rum_services.ts @@ -0,0 +1,47 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('RUM Services', () => { + describe('when there is no data', () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + + describe('when there is data', () => { + before(async () => { + await esArchiver.load('8.0.0'); + await esArchiver.load('rum_8.0.0'); + }); + after(async () => { + await esArchiver.unload('8.0.0'); + await esArchiver.unload('rum_8.0.0'); + }); + + it('returns rum services list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expect(response.body).to.eql(['client', 'opbean-client-rum']); + }); + }); + }); +}