From 05f56dd5aad01129597ebc4a990403b4e62fea83 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 6 Apr 2021 12:13:55 -0400 Subject: [PATCH 01/20] [Uptime] Simplift rtl test (#96296) (#96309) Co-authored-by: Shahzad --- .../shared/exploratory_view/exploratory_view.test.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx index 7e99874f557b3..b90d5115bc41e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -6,7 +6,6 @@ */ import React from 'react'; -import { within } from '@testing-library/react'; import { fireEvent, screen, waitFor } from '@testing-library/dom'; import { render, mockUrlStorage, mockCore } from './rtl_helpers'; import { ExploratoryView } from './exploratory_view'; @@ -56,10 +55,10 @@ describe('ExploratoryView', () => { await waitFor(() => { screen.getByText(/open in lens/i); + }); + + await waitFor(() => { screen.getByText(/select a data type to start building a series\./i); - screen.getByRole('table', { name: /this table contains 1 rows\./i }); - const button = screen.getByRole('button', { name: /add/i }); - within(button).getByText(/add/i); }); await fireEvent.click(screen.getByText(/cancel/i)); From 325241676be31d943d12430dbcefac2eef2f7513 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 6 Apr 2021 13:07:49 -0400 Subject: [PATCH 02/20] [Fleet] Hide many actions for hosted agent policies (#96160) (#96320) ## Summary Fixes items 2a, 4a, 5c2, and 6 from https://github.com/elastic/kibana/issues/91906 ### 2a - On the agents table, remove all actions except for "View agent" [code commit](https://github.com/elastic/kibana/commit/a93dd8cc7b106fedc1c921cebe1ed7f5f999731e) Screen Shot 2021-04-02 at 2 00 34 PM Screen Shot 2021-04-02 at 2 00 41 PM ### 4a & 5c2 On the agent policy list page, remove the "add agent" & "copy policy" actions which appears in the [...] actions menu [code commit](https://github.com/elastic/kibana/commit/f0c267f717f8bd3326254f762dabe2434458a4fa) Screen Shot 2021-04-02 at 2 36 57 PM Screen Shot 2021-04-02 at 2 37 02 PM ### 6 - Do not show the the "revoke token" trash icon [code commit](https://github.com/elastic/kibana/commit/cd05cd0f9781956bc8ed1362650bb5bf898efc0d) Screen Shot 2021-04-02 at 2 15 54 PM Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: John Schulz --- .../agent_policy/components/actions_menu.tsx | 79 ++++++------ .../sections/agents/agent_list_page/index.tsx | 120 ++++++++++-------- .../enrollment_token_list_page/index.tsx | 4 +- 3 files changed, 112 insertions(+), 91 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx index 9ee0b0a7b29ee..bdf49f44f4397 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx @@ -48,6 +48,48 @@ export const AgentPolicyActionMenu = memo<{ return ( {(copyAgentPolicyPrompt) => { + const viewPolicyItem = ( + setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} + key="viewPolicy" + > + + + ); + + const menuItems = agentPolicy?.is_managed + ? [viewPolicyItem] + : [ + setIsEnrollmentFlyoutOpen(true)} + key="enrollAgents" + > + + , + viewPolicyItem, + { + copyAgentPolicyPrompt(agentPolicy, onCopySuccess); + }} + key="copyPolicy" + > + + , + ]; return ( <> {isYamlFlyoutOpen ? ( @@ -80,42 +122,7 @@ export const AgentPolicyActionMenu = memo<{ } : undefined } - items={[ - setIsEnrollmentFlyoutOpen(true)} - key="enrollAgents" - > - - , - setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} - key="viewPolicy" - > - - , - { - copyAgentPolicyPrompt(agentPolicy, onCopySuccess); - }} - key="copyPolicy" - > - - , - ]} + items={menuItems} /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index faf5da5f17f6b..0a35021c7beb0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -55,74 +55,81 @@ const REFRESH_INTERVAL_MS = 30000; const RowActions = React.memo<{ agent: Agent; + agentPolicy?: AgentPolicy; refresh: () => void; onReassignClick: () => void; onUnenrollClick: () => void; onUpgradeClick: () => void; -}>(({ agent, refresh, onReassignClick, onUnenrollClick, onUpgradeClick }) => { +}>(({ agent, agentPolicy, refresh, onReassignClick, onUnenrollClick, onUpgradeClick }) => { const { getHref } = useLink(); const hasWriteCapabilites = useCapabilities().write; const isUnenrolling = agent.status === 'unenrolling'; const kibanaVersion = useKibanaVersion(); const [isMenuOpen, setIsMenuOpen] = useState(false); - return ( - setIsMenuOpen(isOpen)} - items={[ - - - , - { - onReassignClick(); - }} - disabled={!agent.active} - key="reassignPolicy" - > + const menuItems = [ + + + , + ]; + + if (agentPolicy?.is_managed === false) { + menuItems.push( + { + onReassignClick(); + }} + disabled={!agent.active} + key="reassignPolicy" + > + + , + { + onUnenrollClick(); + }} + > + {isUnenrolling ? ( - , - { - onUnenrollClick(); - }} - > - {isUnenrolling ? ( - - ) : ( - - )} - , - { - onUpgradeClick(); - }} - > + ) : ( - , - ]} + )} + , + { + onUpgradeClick(); + }} + > + + + ); + } + return ( + setIsMenuOpen(isOpen)} + items={menuItems} /> ); }); @@ -445,9 +452,14 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { actions: [ { render: (agent: Agent) => { + const agentPolicy = + typeof agent.policy_id === 'string' + ? agentPoliciesIndexedById[agent.policy_id] + : undefined; return ( fetchData()} onReassignClick={() => setAgentToReassign(agent)} onUnenrollClick={() => setAgentToUnenroll(agent)} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx index a19cf714135fc..5d867a1c4c93c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx @@ -242,8 +242,10 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { }), width: '70px', render: (_: any, apiKey: EnrollmentAPIKey) => { + const agentPolicy = agentPolicies.find((c) => c.id === apiKey.policy_id); + const canUnenroll = apiKey.active && !agentPolicy?.is_managed; return ( - apiKey.active && ( + canUnenroll && ( enrollmentAPIKeysRequest.resendRequest()} From b6bc201c13be5113261096eb6a5cd07fb16fb77a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 6 Apr 2021 13:35:35 -0400 Subject: [PATCH 03/20] Convert components to use React.lazy for bundling (#96185) (#96324) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Clint Andrew Hall --- .../actions/copy_to_dashboard_modal.tsx | 4 +- .../public/services/presentation_util.ts | 6 +- .../public/components/dashboard_picker.tsx | 4 + .../public/components/index.tsx | 32 ++++++++ .../saved_object_save_modal_dashboard.tsx | 29 ++----- ...d_object_save_modal_dashboard_selector.tsx | 2 +- .../public/components/types.ts | 24 ++++++ src/plugins/presentation_util/public/index.ts | 8 +- .../application/utils/get_top_nav_config.tsx | 82 +++++++++++-------- ...ed_object_save_modal_dashboard_wrapper.tsx | 5 +- .../public/routes/map_page/top_nav_config.tsx | 19 ++++- 11 files changed, 146 insertions(+), 69 deletions(-) create mode 100644 src/plugins/presentation_util/public/components/index.tsx create mode 100644 src/plugins/presentation_util/public/components/types.ts diff --git a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx index 49b12d46dc9a2..cda2f76930627 100644 --- a/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx +++ b/src/plugins/dashboard/public/application/actions/copy_to_dashboard_modal.tsx @@ -23,7 +23,7 @@ import { EuiOutsideClickDetector, } from '@elastic/eui'; import { DashboardCopyToCapabilities } from './copy_to_dashboard_action'; -import { DashboardPicker } from '../../services/presentation_util'; +import { LazyDashboardPicker, withSuspense } from '../../services/presentation_util'; import { dashboardCopyToDashboardAction } from '../../dashboard_strings'; import { EmbeddableStateTransfer, IEmbeddable } from '../../services/embeddable'; import { createDashboardEditUrl, DashboardConstants } from '../..'; @@ -37,6 +37,8 @@ interface CopyToDashboardModalProps { closeModal: () => void; } +const DashboardPicker = withSuspense(LazyDashboardPicker); + export function CopyToDashboardModal({ PresentationUtilContext, stateTransfer, diff --git a/src/plugins/dashboard/public/services/presentation_util.ts b/src/plugins/dashboard/public/services/presentation_util.ts index 017b455966f13..d3e6c1ebe9eec 100644 --- a/src/plugins/dashboard/public/services/presentation_util.ts +++ b/src/plugins/dashboard/public/services/presentation_util.ts @@ -6,4 +6,8 @@ * Side Public License, v 1. */ -export { PresentationUtilPluginStart, DashboardPicker } from '../../../presentation_util/public'; +export { + PresentationUtilPluginStart, + LazyDashboardPicker, + withSuspense, +} from '../../../presentation_util/public'; diff --git a/src/plugins/presentation_util/public/components/dashboard_picker.tsx b/src/plugins/presentation_util/public/components/dashboard_picker.tsx index d32afca5cedeb..47ba570765028 100644 --- a/src/plugins/presentation_util/public/components/dashboard_picker.tsx +++ b/src/plugins/presentation_util/public/components/dashboard_picker.tsx @@ -99,3 +99,7 @@ export function DashboardPicker(props: DashboardPickerProps) { /> ); } + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default DashboardPicker; diff --git a/src/plugins/presentation_util/public/components/index.tsx b/src/plugins/presentation_util/public/components/index.tsx new file mode 100644 index 0000000000000..1aff029bae846 --- /dev/null +++ b/src/plugins/presentation_util/public/components/index.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { Suspense, ComponentType, ReactElement } from 'react'; +import { EuiLoadingSpinner, EuiErrorBoundary } from '@elastic/eui'; + +/** + * A HOC which supplies React.Suspense with a fallback component, and a `EuiErrorBoundary` to contain errors. + * @param Component A component deferred by `React.lazy` + * @param fallback A fallback component to render while things load; default is `EuiLoadingSpinner` + */ +export const withSuspense =

( + Component: ComponentType

, + fallback: ReactElement | null = +) => (props: P) => ( + + + + + +); + +export const LazyDashboardPicker = React.lazy(() => import('./dashboard_picker')); + +export const LazySavedObjectSaveModalDashboard = React.lazy( + () => import('./saved_object_save_modal_dashboard') +); diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx index 4491be04b1a42..6c36cf8b8e3a7 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx @@ -10,32 +10,15 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { - OnSaveProps, - SaveModalState, - SavedObjectSaveModal, -} from '../../../../plugins/saved_objects/public'; +import { OnSaveProps, SavedObjectSaveModal } from '../../../../plugins/saved_objects/public'; -import './saved_object_save_modal_dashboard.scss'; import { pluginServices } from '../services'; +import { SaveModalDashboardProps } from './types'; import { SaveModalDashboardSelector } from './saved_object_save_modal_dashboard_selector'; -interface SaveModalDocumentInfo { - id?: string; - title: string; - description?: string; -} - -export interface SaveModalDashboardProps { - documentInfo: SaveModalDocumentInfo; - canSaveByReference: boolean; - objectType: string; - onClose: () => void; - onSave: (props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean }) => void; - tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode); -} +import './saved_object_save_modal_dashboard.scss'; -export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { +function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { const { documentInfo, tagOptions, objectType, onClose, canSaveByReference } = props; const { id: documentId } = documentInfo; const initialCopyOnSave = !Boolean(documentId); @@ -136,3 +119,7 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { /> ); } + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default SavedObjectSaveModalDashboard; diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx index 78a1569c02ead..53aaecb070c7a 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx @@ -22,7 +22,7 @@ import { EuiCheckbox, } from '@elastic/eui'; -import { DashboardPicker, DashboardPickerProps } from './dashboard_picker'; +import DashboardPicker, { DashboardPickerProps } from './dashboard_picker'; import './saved_object_save_modal_dashboard.scss'; diff --git a/src/plugins/presentation_util/public/components/types.ts b/src/plugins/presentation_util/public/components/types.ts new file mode 100644 index 0000000000000..7c5c50982f49e --- /dev/null +++ b/src/plugins/presentation_util/public/components/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { OnSaveProps, SaveModalState } from '../../../../plugins/saved_objects/public'; + +interface SaveModalDocumentInfo { + id?: string; + title: string; + description?: string; +} + +export interface SaveModalDashboardProps { + documentInfo: SaveModalDocumentInfo; + canSaveByReference: boolean; + objectType: string; + onClose: () => void; + onSave: (props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean }) => void; + tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode); +} diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index f13807032db3e..457f167dd8228 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -8,14 +8,12 @@ import { PresentationUtilPlugin } from './plugin'; -export { - SavedObjectSaveModalDashboard, - SaveModalDashboardProps, -} from './components/saved_object_save_modal_dashboard'; +export { LazyDashboardPicker, LazySavedObjectSaveModalDashboard, withSuspense } from './components'; -export { DashboardPicker } from './components/dashboard_picker'; +export { SaveModalDashboardProps } from './components/types'; export function plugin() { return new PresentationUtilPlugin(); } + export { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types'; diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index e696bcb5dbe4d..b7c7d63cef98f 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -18,7 +18,10 @@ import { SavedObjectSaveOpts, OnSaveProps, } from '../../../../saved_objects/public'; -import { SavedObjectSaveModalDashboard } from '../../../../presentation_util/public'; +import { + LazySavedObjectSaveModalDashboard, + withSuspense, +} from '../../../../presentation_util/public'; import { unhashUrl } from '../../../../kibana_utils/public'; import { @@ -52,6 +55,8 @@ interface TopNavConfigParams { embeddableId?: string; } +const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); + export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => { if (!anonymousUserCapabilities.visualize) return false; @@ -420,40 +425,47 @@ export const getTopNavConfig = ( const useByRefFlow = !!originatingApp || !dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables; - const saveModal = useByRefFlow ? ( - {}} - originatingApp={originatingApp} - returnToOriginSwitchLabel={ - originatingApp && embeddableId - ? i18n.translate('visualize.topNavMenu.updatePanel', { - defaultMessage: 'Update panel on {originatingAppName}', - values: { - originatingAppName: stateTransfer.getAppNameFromId(originatingApp), - }, - }) - : undefined - } - /> - ) : ( - {}} - /> - ); + let saveModal; + + if (useByRefFlow) { + saveModal = ( + {}} + originatingApp={originatingApp} + returnToOriginSwitchLabel={ + originatingApp && embeddableId + ? i18n.translate('visualize.topNavMenu.updatePanel', { + defaultMessage: 'Update panel on {originatingAppName}', + values: { + originatingAppName: stateTransfer.getAppNameFromId(originatingApp), + }, + }) + : undefined + } + /> + ); + } else { + saveModal = ( + {}} + /> + ); + } + showSaveModal( saveModal, I18nContext, diff --git a/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx b/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx index ee5422fcb90a6..03e2b4d15d0cb 100644 --- a/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx +++ b/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx @@ -9,7 +9,8 @@ import React, { FC, useState, useMemo, useCallback } from 'react'; import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public'; import { SaveModalDashboardProps, - SavedObjectSaveModalDashboard, + LazySavedObjectSaveModalDashboard, + withSuspense, } from '../../../../../src/plugins/presentation_util/public'; import { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/public'; @@ -29,6 +30,8 @@ export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit< onSave: (props: DashboardSaveProps) => void; }; +const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); + export const TagEnhancedSavedObjectSaveModalDashboard: FC = ({ initialTags, onSave, diff --git a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx index 7e0aa59756876..7ac8c3070eb9d 100644 --- a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx @@ -28,7 +28,12 @@ import { import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { SavedMap } from './saved_map'; import { getMapEmbeddableDisplayName } from '../../../common/i18n_getters'; -import { SavedObjectSaveModalDashboard } from '../../../../../../src/plugins/presentation_util/public'; +import { + LazySavedObjectSaveModalDashboard, + withSuspense, +} from '../../../../../../src/plugins/presentation_util/public'; + +const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); export function getTopNavConfig({ savedMap, @@ -192,21 +197,27 @@ export function getTopNavConfig({ }), }; const PresentationUtilContext = getPresentationUtilContext(); - const saveModal = - savedMap.getOriginatingApp() || !getIsAllowByValueEmbeddables() ? ( + + let saveModal; + + if (savedMap.getOriginatingApp() || !getIsAllowByValueEmbeddables()) { + saveModal = ( - ) : ( + ); + } else { + saveModal = ( ); + } showSaveModal(saveModal, getCoreI18n().Context, PresentationUtilContext); }, From a97e0cbbe40fc8d66c1041a65339166874aa9565 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Tue, 6 Apr 2021 12:44:17 -0500 Subject: [PATCH 04/20] Add anomaly detection telemetry (#95802) (#96326) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ml/anomaly_detection/job_setup_screen.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx index a210831eef865..f6d739078002e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx @@ -24,6 +24,7 @@ import { FixedDatePicker } from '../../../../../../components/fixed_datepicker'; import { DEFAULT_K8S_PARTITION_FIELD } from '../../../../../../containers/ml/modules/metrics_k8s/module_descriptor'; import { MetricsExplorerKueryBar } from '../../../../metrics_explorer/components/kuery_bar'; import { convertKueryToElasticSearchQuery } from '../../../../../../utils/kuery'; +import { useUiTracker } from '../../../../../../../../observability/public'; interface Props { jobType: 'hosts' | 'kubernetes'; @@ -40,6 +41,7 @@ export const JobSetupScreen = (props: Props) => { const k = useMetricK8sModuleContext(); const [filter, setFilter] = useState(''); const [filterQuery, setFilterQuery] = useState(''); + const trackMetric = useUiTracker({ app: 'infra_metrics' }); const { createDerivedIndexPattern } = useSourceViaHttp({ sourceId: 'default', }); @@ -137,9 +139,25 @@ export const JobSetupScreen = (props: Props) => { useEffect(() => { if (setupStatus.type === 'succeeded') { + if (props.jobType === 'kubernetes') { + trackMetric({ metric: 'metrics_ml_anomaly_detection_k8s_enabled' }); + if ( + partitionField && + (partitionField.length !== 1 || partitionField[0] !== DEFAULT_K8S_PARTITION_FIELD) + ) { + trackMetric({ metric: 'metrics_ml_anomaly_detection_k8s_partition_changed' }); + } + } else { + trackMetric({ metric: 'metrics_ml_anomaly_detection_hosts_enabled' }); + if (partitionField) { + trackMetric({ metric: 'metrics_ml_anomaly_detection_hosts_partition_changed' }); + } + trackMetric({ metric: 'metrics_ml_anomaly_detection_hosts_enabled' }); + } + goHome(); } - }, [setupStatus, goHome]); + }, [setupStatus, props.jobType, partitionField, trackMetric, goHome]); return ( <> From 1619a6a54bf31ce2983567afecae71283882bfc5 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Tue, 6 Apr 2021 10:47:21 -0700 Subject: [PATCH 05/20] [Metrics UI] Observability Overview Host Summary (#90879) (#96328) * [Metrics UI] Observability Overview Host Summary * Adding UI elements * Adding logos * Changing the size of the request * Change to new ECS fields for network traffic * Adding logos to HostLink component * Round seconds * fixing data handler test * Fixing test for metrics_overview_fetchers * Adding types for SVG to observability * Adding i18n support to table labels * removing unused translations * move back to host.network.(in,out).bytes * Adding changes to source from #95334 * Fixing source type * Removing unintentional change. * Maybe, fixing types * removing svg typings Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../infra/common/http_api/overview_api.ts | 47 + .../metrics_overview_fetchers.test.ts.snap | 16 +- .../common/components/alert_preview.tsx | 2 +- .../public/metrics_overview_fetchers.test.ts | 4 +- .../infra/public/metrics_overview_fetchers.ts | 61 +- .../plugins/infra/public/test_utils/index.ts | 10 +- .../infra/server/routes/overview/index.ts | 85 +- ...nvert_es_response_to_top_nodes_response.ts | 43 + .../overview/lib/create_top_nodes_query.ts | 127 ++ .../lib/get_matadata_from_node_bucket.ts | 26 + .../routes/overview/lib/get_top_nodes.ts | 26 + .../infra/server/routes/overview/lib/types.ts | 48 + .../app/section/metrics/host_link.tsx | 71 + .../components/app/section/metrics/index.tsx | 237 ++- .../metrics/lib/format_duration.test.ts | 27 + .../section/metrics/lib/format_duration.ts | 23 + .../app/section/metrics/logos/aix.svg | 83 + .../app/section/metrics/logos/android.svg | 19 + .../app/section/metrics/logos/darwin.svg | 1 + .../app/section/metrics/logos/dragonfly.svg | 1 + .../app/section/metrics/logos/freebsd.svg | 8 + .../app/section/metrics/logos/illumos.svg | 1 + .../app/section/metrics/logos/linux.svg | 1532 +++++++++++++++++ .../app/section/metrics/logos/netbsd.svg | 57 + .../app/section/metrics/logos/solaris.svg | 54 + .../section/metrics/metric_with_sparkline.tsx | 61 + .../observability/public/data_handler.test.ts | 105 +- .../pages/overview/mock/metrics.mock.ts | 18 +- .../typings/fetch_overview_data/index.ts | 32 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 31 files changed, 2512 insertions(+), 319 deletions(-) create mode 100644 x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts create mode 100644 x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts create mode 100644 x-pack/plugins/infra/server/routes/overview/lib/get_matadata_from_node_bucket.ts create mode 100644 x-pack/plugins/infra/server/routes/overview/lib/get_top_nodes.ts create mode 100644 x-pack/plugins/infra/server/routes/overview/lib/types.ts create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/host_link.tsx create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.test.ts create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.ts create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/aix.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/android.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/darwin.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/dragonfly.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/freebsd.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/illumos.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/linux.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/netbsd.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/logos/solaris.svg create mode 100644 x-pack/plugins/observability/public/components/app/section/metrics/metric_with_sparkline.tsx diff --git a/x-pack/plugins/infra/common/http_api/overview_api.ts b/x-pack/plugins/infra/common/http_api/overview_api.ts index e609256ed837a..551681f6fc139 100644 --- a/x-pack/plugins/infra/common/http_api/overview_api.ts +++ b/x-pack/plugins/infra/common/http_api/overview_api.ts @@ -34,6 +34,53 @@ export const OverviewRequestRT = rt.type({ }), }); +export const TopNodesRequestRT = rt.intersection([ + rt.type({ + sourceId: rt.string, + size: rt.number, + bucketSize: rt.string, + timerange: rt.type({ + from: rt.number, + to: rt.number, + }), + }), + rt.partial({ sort: rt.string, sortDirection: rt.string }), +]); + +export type TopNodesRequest = rt.TypeOf; + +const numberOrNullRT = rt.union([rt.number, rt.null]); +const stringOrNullRT = rt.union([rt.string, rt.null]); + +export const TopNodesTimeseriesRowRT = rt.type({ + timestamp: rt.number, + cpu: numberOrNullRT, + iowait: numberOrNullRT, + load: numberOrNullRT, + rx: numberOrNullRT, + tx: numberOrNullRT, +}); + +export const TopNodesSeriesRT = rt.type({ + id: rt.string, + name: stringOrNullRT, + platform: stringOrNullRT, + provider: stringOrNullRT, + cpu: numberOrNullRT, + iowait: numberOrNullRT, + load: numberOrNullRT, + uptime: numberOrNullRT, + rx: numberOrNullRT, + tx: numberOrNullRT, + timeseries: rt.array(TopNodesTimeseriesRowRT), +}); + +export const TopNodesResponseRT = rt.type({ + series: rt.array(TopNodesSeriesRT), +}); + +export type TopNodesResponse = rt.TypeOf; + export type OverviewMetricType = rt.TypeOf; export type OverviewMetric = rt.TypeOf; export type OverviewResponse = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap b/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap index db931905b25db..2904f37141325 100644 --- a/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap +++ b/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap @@ -3,19 +3,7 @@ exports[`Metrics UI Observability Homepage Functions createMetricsFetchData() should just work 1`] = ` Object { "appLink": "/app/metrics/inventory?waffleTime=(currentTime:1593696311629,isAutoReloading:!f)", - "stats": Object { - "cpu": Object { - "type": "percent", - "value": 0.10691011235955057, - }, - "hosts": Object { - "type": "number", - "value": 1, - }, - "memory": Object { - "type": "percent", - "value": 0.5389775280898876, - }, - }, + "series": Array [], + "sort": [Function], } `; diff --git a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx index 410ad15b356ea..7d7b0004fa068 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx +++ b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx @@ -174,7 +174,7 @@ export const AlertPreview: React.FC = (props) => { title={ } > diff --git a/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts b/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts index 00ff863d205c1..e6ffbc30fe24d 100644 --- a/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts +++ b/x-pack/plugins/infra/public/metrics_overview_fetchers.test.ts @@ -69,9 +69,11 @@ describe('Metrics UI Observability Homepage Functions', () => { bucketSize, }); expect(core.http.post).toHaveBeenCalledTimes(1); - expect(core.http.post).toHaveBeenCalledWith('/api/metrics/overview', { + expect(core.http.post).toHaveBeenCalledWith('/api/metrics/overview/top', { body: JSON.stringify({ sourceId: 'default', + bucketSize, + size: 5, timerange: { from: startTime.valueOf(), to: endTime.valueOf(), diff --git a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts index bcc2eec504209..4f5b73d685591 100644 --- a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts @@ -5,8 +5,16 @@ * 2.0. */ +/* + * Copyright + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { FetchDataParams, MetricsFetchDataResponse } from '../../observability/public'; -import { OverviewRequest, OverviewResponse } from '../common/http_api/overview_api'; +import { TopNodesRequest, TopNodesResponse } from '../common/http_api/overview_api'; import { InfraClientCoreSetup } from './types'; export const createMetricsHasData = ( @@ -20,38 +28,33 @@ export const createMetricsHasData = ( export const createMetricsFetchData = ( getStartServices: InfraClientCoreSetup['getStartServices'] -) => async ({ absoluteTime }: FetchDataParams): Promise => { +) => async ({ absoluteTime, bucketSize }: FetchDataParams): Promise => { const [coreServices] = await getStartServices(); const { http } = coreServices; - const { start, end } = absoluteTime; - - const overviewRequest: OverviewRequest = { - sourceId: 'default', - timerange: { - from: start, - to: end, - }, - }; + const makeRequest = async (overrides: Partial = {}) => { + const { start, end } = absoluteTime; - const results = await http.post('/api/metrics/overview', { - body: JSON.stringify(overviewRequest), - }); - return { - appLink: `/app/metrics/inventory?waffleTime=(currentTime:${end},isAutoReloading:!f)`, - stats: { - hosts: { - type: 'number', - value: results.stats.hosts.value, + const overviewRequest: TopNodesRequest = { + sourceId: 'default', + bucketSize, + size: 5, + timerange: { + from: start, + to: end, }, - cpu: { - type: 'percent', - value: results.stats.cpu.value, - }, - memory: { - type: 'percent', - value: results.stats.memory.value, - }, - }, + ...overrides, + }; + const results = await http.post('/api/metrics/overview/top', { + body: JSON.stringify(overviewRequest), + }); + return { + appLink: `/app/metrics/inventory?waffleTime=(currentTime:${end},isAutoReloading:!f)`, + series: results.series, + sort: async (by: string, direction: string) => + makeRequest({ sort: by, sortDirection: direction }), + }; }; + + return await makeRequest(); }; diff --git a/x-pack/plugins/infra/public/test_utils/index.ts b/x-pack/plugins/infra/public/test_utils/index.ts index 22e7d647e0965..2c88bfecf0c5d 100644 --- a/x-pack/plugins/infra/public/test_utils/index.ts +++ b/x-pack/plugins/infra/public/test_utils/index.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { TopNodesResponse } from '../../common/http_api/overview_api'; + export const FAKE_SNAPSHOT_RESPONSE = { nodes: [ { @@ -309,10 +311,6 @@ export const FAKE_SNAPSHOT_RESPONSE = { interval: '300s', }; -export const FAKE_OVERVIEW_RESPONSE = { - stats: { - hosts: { type: 'number', value: 1 }, - cpu: { type: 'percent', value: 0.10691011235955057 }, - memory: { type: 'percent', value: 0.5389775280898876 }, - }, +export const FAKE_OVERVIEW_RESPONSE: TopNodesResponse = { + series: [], }; diff --git a/x-pack/plugins/infra/server/routes/overview/index.ts b/x-pack/plugins/infra/server/routes/overview/index.ts index fe988abcc2883..fa0e0f4d09aff 100644 --- a/x-pack/plugins/infra/server/routes/overview/index.ts +++ b/x-pack/plugins/infra/server/routes/overview/index.ts @@ -4,25 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import Boom from '@hapi/boom'; -import { schema } from '@kbn/config-schema'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; -import { findInventoryFields } from '../../../common/inventory_models'; -import { throwErrors } from '../../../common/runtime_types'; -import { OverviewRequestRT } from '../../../common/http_api/overview_api'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { TopNodesRequestRT } from '../../../common/http_api/overview_api'; import { InfraBackendLibs } from '../../lib/infra_types'; import { createSearchClient } from '../../lib/create_search_client'; - -const escapeHatch = schema.object({}, { unknowns: 'allow' }); - -interface OverviewESAggResponse { - memory: { value: number }; - hosts: { value: number }; - cpu: { value: number }; -} +import { queryTopNodes } from './lib/get_top_nodes'; export const initOverviewRoute = (libs: InfraBackendLibs) => { const { framework } = libs; @@ -30,76 +16,23 @@ export const initOverviewRoute = (libs: InfraBackendLibs) => { framework.registerRoute( { method: 'post', - path: '/api/metrics/overview', + path: '/api/metrics/overview/top', validate: { - body: escapeHatch, + body: createValidationFunction(TopNodesRequestRT), }, }, async (requestContext, request, response) => { - const overviewRequest = pipe( - OverviewRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - + const options = request.body; const client = createSearchClient(requestContext, framework); const source = await libs.sources.getSourceConfiguration( requestContext.core.savedObjects.client, - overviewRequest.sourceId + options.sourceId ); - const inventoryModelFields = findInventoryFields('host', source.configuration.fields); - - const params = { - index: source.configuration.metricAlias, - body: { - query: { - range: { - [source.configuration.fields.timestamp]: { - gte: overviewRequest.timerange.from, - lte: overviewRequest.timerange.to, - format: 'epoch_millis', - }, - }, - }, - aggs: { - hosts: { - cardinality: { - field: inventoryModelFields.id, - }, - }, - cpu: { - avg: { - field: 'system.cpu.total.norm.pct', - }, - }, - memory: { - avg: { - field: 'system.memory.actual.used.pct', - }, - }, - }, - }, - }; - - const esResponse = await client<{}, OverviewESAggResponse>(params); + const topNResponse = await queryTopNodes(options, client, source); return response.ok({ - body: { - stats: { - hosts: { - type: 'number', - value: esResponse.aggregations?.hosts.value ?? 0, - }, - cpu: { - type: 'percent', - value: esResponse.aggregations?.cpu.value ?? 0, - }, - memory: { - type: 'percent', - value: esResponse.aggregations?.memory.value ?? 0, - }, - }, - }, + body: topNResponse, }); } ); diff --git a/x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts b/x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts new file mode 100644 index 0000000000000..c3b4693372db3 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TopNodesResponse } from '../../../../common/http_api/overview_api'; +import { InfraDatabaseSearchResponse } from '../../../lib/adapters/framework'; +import { getMetadataFromNodeBucket } from './get_matadata_from_node_bucket'; +import { ESResponseForTopNodes } from './types'; + +export const convertESResponseToTopNodesResponse = ( + response: InfraDatabaseSearchResponse<{}, ESResponseForTopNodes> +): TopNodesResponse => { + if (!response.aggregations) { + return { series: [] }; + } + return { + series: response.aggregations.nodes.buckets.map((node) => { + return { + id: node.key, + ...getMetadataFromNodeBucket(node), + timeseries: node.timeseries.buckets.map((bucket) => { + return { + timestamp: bucket.key, + cpu: bucket.cpu.value, + iowait: bucket.iowait.value, + load: bucket.load.value, + rx: bucket.rx.value, + tx: bucket.tx.value, + }; + }), + cpu: node.cpu.value, + iowait: node.iowait.value, + load: node.load.value, + uptime: node.uptime.value, + rx: node.rx.value, + tx: node.tx.value, + }; + }), + }; +}; diff --git a/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts b/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts new file mode 100644 index 0000000000000..a9245a8c8ce75 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MetricsSourceConfiguration } from '../../../../common/metrics_sources'; +import { TopNodesRequest } from '../../../../common/http_api/overview_api'; + +export const createTopNodesQuery = ( + options: TopNodesRequest, + source: MetricsSourceConfiguration +) => { + const sortByHost = options.sort && options.sort === 'name'; + const sortField = sortByHost ? '_key' : options.sort ?? 'uptime'; + const sortDirection = options.sortDirection ?? 'asc'; + return { + size: 0, + query: { + bool: { + filter: [ + { + range: { + [source.configuration.fields.timestamp]: { + gte: options.timerange.from, + lte: options.timerange.to, + }, + }, + }, + { + match_phrase: { 'event.module': 'system' }, + }, + ], + }, + }, + aggs: { + nodes: { + terms: { + field: 'host.name', + size: options.size, + order: { [sortField]: sortDirection }, + }, + aggs: { + metadata: { + top_metrics: { + metrics: [ + { field: 'host.os.platform' }, + { field: 'host.name' }, + { field: 'cloud.provider' }, + ], + sort: { '@timestamp': 'desc' }, + size: 1, + }, + }, + uptime: { + max: { + field: 'system.uptime.duration.ms', + }, + }, + cpu: { + avg: { + field: 'system.cpu.total.pct', + }, + }, + iowait: { + avg: { + field: 'system.core.iowait.pct', + }, + }, + load: { + avg: { + field: 'system.load.15', + }, + }, + rx: { + sum: { + field: 'host.network.in.bytes', + }, + }, + tx: { + sum: { + field: 'host.network.out.bytes', + }, + }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: '1m', + extended_bounds: { + min: options.timerange.from, + max: options.timerange.to, + }, + }, + aggs: { + cpu: { + avg: { + field: 'host.cpu.pct', + }, + }, + iowait: { + avg: { + field: 'system.core.iowait.pct', + }, + }, + load: { + avg: { + field: 'system.load.15', + }, + }, + rx: { + rate: { + field: 'host.network.ingress.bytes', + }, + }, + tx: { + rate: { + field: 'host.network.egress.bytes', + }, + }, + }, + }, + }, + }, + }, + }; +}; diff --git a/x-pack/plugins/infra/server/routes/overview/lib/get_matadata_from_node_bucket.ts b/x-pack/plugins/infra/server/routes/overview/lib/get_matadata_from_node_bucket.ts new file mode 100644 index 0000000000000..2f1a2a4cded8d --- /dev/null +++ b/x-pack/plugins/infra/server/routes/overview/lib/get_matadata_from_node_bucket.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { NodeBucket } from './types'; + +interface Metadata { + name: string | null; + provider: string | null; + platform: string | null; +} + +export const getMetadataFromNodeBucket = (node: NodeBucket): Metadata => { + const metadata = node.metadata.top[0]; + if (!metadata) { + return { name: null, provider: null, platform: null }; + } + return { + name: metadata.metrics['host.name'] || null, + provider: metadata.metrics['cloud.provider'] || null, + platform: metadata.metrics['host.os.platform'] || null, + }; +}; diff --git a/x-pack/plugins/infra/server/routes/overview/lib/get_top_nodes.ts b/x-pack/plugins/infra/server/routes/overview/lib/get_top_nodes.ts new file mode 100644 index 0000000000000..4d46479ac7a54 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/overview/lib/get_top_nodes.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { TopNodesRequest } from '../../../../common/http_api/overview_api'; +import { MetricsSourceConfiguration } from '../../../../common/metrics_sources'; +import { ESSearchClient } from '../../../lib/metrics/types'; +import { convertESResponseToTopNodesResponse } from './convert_es_response_to_top_nodes_response'; +import { createTopNodesQuery } from './create_top_nodes_query'; +import { ESResponseForTopNodes } from './types'; + +export const queryTopNodes = async ( + options: TopNodesRequest, + client: ESSearchClient, + source: MetricsSourceConfiguration +) => { + const params = { + index: source.configuration.metricAlias, + body: createTopNodesQuery(options, source), + }; + + const response = await client<{}, ESResponseForTopNodes>(params); + return convertESResponseToTopNodesResponse(response); +}; diff --git a/x-pack/plugins/infra/server/routes/overview/lib/types.ts b/x-pack/plugins/infra/server/routes/overview/lib/types.ts new file mode 100644 index 0000000000000..d5342d0757ad6 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/overview/lib/types.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +type NumberOrNull = number | null; + +interface TopMetric { + sort: string[]; + metrics: Record; +} + +interface NodeMetric { + value: NumberOrNull; +} + +interface NodeMetrics { + doc_count: number; + uptime: NodeMetric; + cpu: NodeMetric; + iowait: NodeMetric; + load: NodeMetric; + rx: NodeMetric; + tx: NodeMetric; +} + +interface TimeSeriesMetric extends NodeMetrics { + key_as_string: string; + key: number; +} + +export interface NodeBucket extends NodeMetrics { + key: string; + metadata: { + top: TopMetric[]; + }; + timeseries: { + buckets: TimeSeriesMetric[]; + }; +} + +export interface ESResponseForTopNodes { + nodes: { + buckets: NodeBucket[]; + }; +} diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/host_link.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/host_link.tsx new file mode 100644 index 0000000000000..921cec4222ea0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/host_link.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiIcon } from '@elastic/eui'; +import React from 'react'; +import { StringOrNull } from '../../../..'; + +import aixLogo from './logos/aix.svg'; +import androidLogo from './logos/android.svg'; +import darwinLogo from './logos/darwin.svg'; +import dragonflyLogo from './logos/dragonfly.svg'; +import freebsdLogo from './logos/freebsd.svg'; +import illumosLogo from './logos/illumos.svg'; +import linuxLogo from './logos/linux.svg'; +import solarisLogo from './logos/solaris.svg'; +import netbsdLogo from './logos/netbsd.svg'; + +interface Props { + name: StringOrNull; + id: StringOrNull; + provider: StringOrNull; + platform: StringOrNull; + timerange: { from: number; to: number }; +} + +export function HostLink({ name, id, provider, platform, timerange }: Props) { + const providerLogo = + provider === 'aws' + ? 'logoAWS' + : provider === 'gcp' + ? 'logoGCP' + : provider === 'azure' + ? 'logoAzure' + : 'compute'; + + const platformLogo = + platform === 'darwin' + ? darwinLogo + : platform === 'windows' + ? 'logoWindows' + : platform === 'linux' + ? linuxLogo + : platform === 'aix' + ? aixLogo + : platform === 'andriod' + ? androidLogo + : platform === 'dragonfly' + ? dragonflyLogo + : platform === 'illumos' + ? illumosLogo + : platform === 'freebsd' + ? freebsdLogo + : platform === 'solaris' + ? solarisLogo + : platform === 'netbsd' + ? netbsdLogo + : 'empty'; + const link = `../../app/metrics/link-to/host-detail/${id}?from=${timerange.from}&to=${timerange.to}`; + return ( + <> + {platformLogo !== null && } +   + {providerLogo !== null && } +   + {name} + + ); +} diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index dfdede6e7b32f..5a642084733c7 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -5,49 +5,56 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer } from '@elastic/eui'; +import { + Criteria, + Direction, + EuiBasicTable, + EuiBasicTableColumn, + EuiTableSortingType, +} from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; -import React, { useContext } from 'react'; -import styled, { ThemeContext } from 'styled-components'; +import React, { useState, useCallback } from 'react'; +import { + MetricsFetchDataResponse, + MetricsFetchDataSeries, + NumberOrNull, + StringOrNull, +} from '../../../..'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; import { useTimeRange } from '../../../../hooks/use_time_range'; -import { StyledStat } from '../../styled_stat'; +import { HostLink } from './host_link'; +import { formatDuration } from './lib/format_duration'; +import { MetricWithSparkline } from './metric_with_sparkline'; + +const SPARK_LINE_COLUMN_WIDTH = '120px'; +const COLOR_ORANGE = 7; +const COLOR_BLUE = 1; +const COLOR_GREEN = 0; +const COLOR_PURPLE = 3; interface Props { bucketSize?: string; } -/** - * EuiProgress doesn't support custom color, when it does this component can be removed. - */ -const StyledProgress = styled.div<{ color?: string }>` - progress { - &.euiProgress--native { - &::-webkit-progress-value { - background-color: ${(props) => props.color}; - } +const percentFormatter = (value: NumberOrNull) => + value === null ? '' : numeral(value).format('0[.0]%'); - &::-moz-progress-bar { - background-color: ${(props) => props.color}; - } - } +const numberFormatter = (value: NumberOrNull) => + value === null ? '' : numeral(value).format('0[.0]'); - &.euiProgress--indeterminate { - &:before { - background-color: ${(props) => props.color}; - } - } - } -`; +const bytesPerSecondFormatter = (value: NumberOrNull) => + value === null ? '' : numeral(value).format('0b') + '/s'; export function MetricsSection({ bucketSize }: Props) { - const theme = useContext(ThemeContext); const { forceUpdate, hasData } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + const [sortDirection, setSortDirection] = useState('asc'); + const [sortField, setSortField] = useState('uptime'); + const [sortedData, setSortedData] = useState(null); const { data, status } = useFetcher( () => { @@ -64,16 +71,138 @@ export function MetricsSection({ bucketSize }: Props) { [bucketSize, relativeStart, relativeEnd, forceUpdate] ); + const handleTableChange = useCallback( + ({ sort }: Criteria) => { + if (sort) { + const { field, direction } = sort; + setSortField(field); + setSortDirection(direction); + if (data) { + (async () => { + const response = await data.sort(field, direction); + setSortedData(response || null); + })(); + } + } + }, + [data, setSortField, setSortDirection] + ); + if (!hasData.infra_metrics?.hasData) { return null; } const isLoading = status === FETCH_STATUS.LOADING; + const isPending = status === FETCH_STATUS.LOADING; + if (isLoading || isPending) { + return

Loading
; + } + + if (!data) { + return
No Data
; + } + + const columns: Array> = [ + { + field: 'uptime', + name: i18n.translate('xpack.observability.overview.metrics.colunms.uptime', { + defaultMessage: 'Uptime', + }), + sortable: true, + width: '80px', + render: (value: NumberOrNull) => (value == null ? 'N/A' : formatDuration(value / 1000)), + }, + { + field: 'name', + name: i18n.translate('xpack.observability.overview.metrics.colunms.hostname', { + defaultMessage: 'Hostname', + }), + sortable: true, + truncateText: true, + isExpander: true, + render: (value: StringOrNull, record: MetricsFetchDataSeries) => ( + + ), + }, + { + field: 'cpu', + name: i18n.translate('xpack.observability.overview.metrics.colunms.cpu', { + defaultMessage: 'CPU %', + }), + sortable: true, + width: SPARK_LINE_COLUMN_WIDTH, + render: (value: NumberOrNull, record: MetricsFetchDataSeries) => ( + + ), + }, + { + field: 'load', + name: i18n.translate('xpack.observability.overview.metrics.colunms.load15', { + defaultMessage: 'Load 15', + }), + sortable: true, + width: SPARK_LINE_COLUMN_WIDTH, + render: (value: NumberOrNull, record: MetricsFetchDataSeries) => ( + + ), + }, + { + field: 'rx', + name: 'RX', + sortable: true, + width: SPARK_LINE_COLUMN_WIDTH, + render: (value: NumberOrNull, record: MetricsFetchDataSeries) => ( + + ), + }, + { + field: 'tx', + name: 'TX', + sortable: true, + width: SPARK_LINE_COLUMN_WIDTH, + render: (value: NumberOrNull, record: MetricsFetchDataSeries) => ( + + ), + }, + ]; + + const sorting: EuiTableSortingType = { + sort: { field: sortField, direction: sortDirection }, + }; - const { appLink, stats } = data || {}; + const viewData = sortedData || data; - const cpuColor = theme.eui.euiColorVis7; - const memoryColor = theme.eui.euiColorVis0; + const { appLink } = data || {}; return ( - - - - - - - - - - - - - - - - - - - - - + ); } diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.test.ts b/x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.test.ts new file mode 100644 index 0000000000000..b4b03b2194ef2 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.test.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { formatDuration } from './format_duration'; + +describe('formatDuration(seconds)', () => { + it('should work for less then a minute', () => { + expect(formatDuration(56)).toBe('56s'); + }); + + it('should work for less then a hour', () => { + expect(formatDuration(2000)).toBe('33m 20s'); + }); + + it('should work for less then a day', () => { + expect(formatDuration(74566)).toBe('20h 42m'); + }); + + it('should work for more then a day', () => { + expect(formatDuration(86400 * 3 + 3600 * 4)).toBe('3d 4h'); + expect(formatDuration(86400 * 419 + 3600 * 6)).toBe('419d 6h'); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.ts b/x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.ts new file mode 100644 index 0000000000000..29fb1dcbd1b52 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/lib/format_duration.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const MINUTE = 60; +const HOUR = 3600; +const DAY = 86400; + +export const formatDuration = (seconds: number) => { + if (seconds < MINUTE) { + return `${Math.floor(seconds)}s`; + } + if (seconds < HOUR) { + return `${Math.floor(seconds / MINUTE)}m ${Math.floor(seconds % MINUTE)}s`; + } + if (seconds < DAY) { + return `${Math.floor(seconds / HOUR)}h ${Math.floor((seconds % HOUR) / MINUTE)}m`; + } + return `${Math.floor(seconds / DAY)}d ${Math.floor((seconds % DAY) / HOUR)}h`; +}; diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/aix.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/aix.svg new file mode 100644 index 0000000000000..6d26c99bec674 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/aix.svg @@ -0,0 +1,83 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/android.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/android.svg new file mode 100644 index 0000000000000..f53491803db44 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/android.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/darwin.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/darwin.svg new file mode 100644 index 0000000000000..73630c9ba2630 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/darwin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/dragonfly.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/dragonfly.svg new file mode 100644 index 0000000000000..4f026bb4dbac5 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/dragonfly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/freebsd.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/freebsd.svg new file mode 100644 index 0000000000000..4516c4e302ba4 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/freebsd.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/illumos.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/illumos.svg new file mode 100644 index 0000000000000..c9ab6aed30151 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/illumos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/linux.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/linux.svg new file mode 100644 index 0000000000000..c0a92e0c0f404 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/linux.svg @@ -0,0 +1,1532 @@ + + + + Tux + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Tux + 20 June 2012 + + + Garrett LeSage + + + + + + Larry Ewing, the creator of the original Tux graphic + + + + + tux + Linux + penguin + logo + + + + + Larry Ewing, Garrett LeSage + + + https://github.com/garrett/Tux + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/netbsd.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/netbsd.svg new file mode 100644 index 0000000000000..7cc046187eb60 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/netbsd.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/logos/solaris.svg b/x-pack/plugins/observability/public/components/app/section/metrics/logos/solaris.svg new file mode 100644 index 0000000000000..1a211689f86f3 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/logos/solaris.svg @@ -0,0 +1,54 @@ + + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: Open Icon Library + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/metric_with_sparkline.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/metric_with_sparkline.tsx new file mode 100644 index 0000000000000..3cb61f85d57f0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/metric_with_sparkline.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Chart, Settings, AreaSeries } from '@elastic/charts'; +import { EuiIcon, EuiTextColor } from '@elastic/eui'; +import React, { useContext } from 'react'; +import { + EUI_CHARTS_THEME_DARK, + EUI_CHARTS_THEME_LIGHT, + EUI_SPARKLINE_THEME_PARTIAL, +} from '@elastic/eui/dist/eui_charts_theme'; +import { ThemeContext } from 'styled-components'; + +import { NumberOrNull } from '../../../..'; + +interface Props { + id: string; + value: NumberOrNull; + timeseries: any[]; + formatter: (value: NumberOrNull) => string; + color: number; +} +export function MetricWithSparkline({ id, formatter, value, timeseries, color }: Props) { + const themeCTX = useContext(ThemeContext); + const isDarkTheme = (themeCTX && themeCTX.darkMode) || false; + const theme = [ + EUI_SPARKLINE_THEME_PARTIAL, + isDarkTheme ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme, + ]; + + const colors = theme[1].colors?.vizColors ?? []; + + if (!value) { + return ( + + +  N/A + + ); + } + return ( + <> + + + + +   + {formatter(value)} + + ); +} diff --git a/x-pack/plugins/observability/public/data_handler.test.ts b/x-pack/plugins/observability/public/data_handler.test.ts index 868e5be2b6317..bba2083aceb80 100644 --- a/x-pack/plugins/observability/public/data_handler.test.ts +++ b/x-pack/plugins/observability/public/data_handler.test.ts @@ -321,56 +321,18 @@ describe('registerDataHandler', () => { }); describe('Metrics', () => { + const makeRequestResponse = { + title: 'metrics', + appLink: '/metrics', + sort: () => makeRequest(), + series: [], + }; + const makeRequest = async () => { + return makeRequestResponse; + }; registerDataHandler({ appName: 'infra_metrics', - fetchData: async () => { - return { - title: 'metrics', - appLink: '/metrics', - stats: { - hosts: { - label: 'hosts', - type: 'number', - value: 1, - }, - cpu: { - label: 'cpu', - type: 'number', - value: 1, - }, - memory: { - label: 'memory', - type: 'number', - value: 1, - }, - disk: { - label: 'disk', - type: 'number', - value: 1, - }, - inboundTraffic: { - label: 'inboundTraffic', - type: 'number', - value: 1, - }, - outboundTraffic: { - label: 'outboundTraffic', - type: 'number', - value: 1, - }, - }, - series: { - inboundTraffic: { - label: 'inbound Traffic', - coordinates: [{ x: 1 }], - }, - outboundTraffic: { - label: 'outbound Traffic', - coordinates: [{ x: 1 }], - }, - }, - }; - }, + fetchData: makeRequest, hasData: async () => true, }); @@ -383,52 +345,7 @@ describe('registerDataHandler', () => { it('returns data when fetchData is called', async () => { const dataHandler = getDataHandler('infra_metrics'); const response = await dataHandler?.fetchData(params); - expect(response).toEqual({ - title: 'metrics', - appLink: '/metrics', - stats: { - hosts: { - label: 'hosts', - type: 'number', - value: 1, - }, - cpu: { - label: 'cpu', - type: 'number', - value: 1, - }, - memory: { - label: 'memory', - type: 'number', - value: 1, - }, - disk: { - label: 'disk', - type: 'number', - value: 1, - }, - inboundTraffic: { - label: 'inboundTraffic', - type: 'number', - value: 1, - }, - outboundTraffic: { - label: 'outboundTraffic', - type: 'number', - value: 1, - }, - }, - series: { - inboundTraffic: { - label: 'inbound Traffic', - coordinates: [{ x: 1 }], - }, - outboundTraffic: { - label: 'outbound Traffic', - coordinates: [{ x: 1 }], - }, - }, - }); + expect(response).toEqual(makeRequestResponse); }); it('returns true when hasData is called', async () => { diff --git a/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts index 82f804ba1a938..f88b89e75389e 100644 --- a/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts @@ -12,19 +12,13 @@ export const fetchMetricsData: FetchData = () => { }; const response: MetricsFetchDataResponse = { - appLink: '/app/apm', - stats: { - hosts: { value: 11, type: 'number' }, - cpu: { value: 0.8, type: 'percent' }, - memory: { value: 0.362, type: 'percent' }, - }, + appLink: '/app/metrics', + sort: async () => response, + series: [], }; export const emptyResponse: MetricsFetchDataResponse = { - appLink: '/app/apm', - stats: { - hosts: { value: 0, type: 'number' }, - cpu: { value: 0, type: 'percent' }, - memory: { value: 0, type: 'percent' }, - }, + appLink: '/app/metrics', + sort: async () => emptyResponse, + series: [], }; diff --git a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts index e9960833a1c4f..726c83d0c2256 100644 --- a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts @@ -7,7 +7,6 @@ import { ObservabilityApp } from '../../../typings/common'; import { UXMetrics } from '../../components/shared/core_web_vitals'; - export interface Stat { type: 'number' | 'percent' | 'bytesPerSecond'; value: number; @@ -67,12 +66,33 @@ export interface LogsFetchDataResponse extends FetchDataResponse { series: Record; } +export type StringOrNull = string | null; +export type NumberOrNull = number | null; + +export interface MetricsFetchDataSeries { + id: string; + name: StringOrNull; + platform: StringOrNull; + provider: StringOrNull; + cpu: NumberOrNull; + iowait: NumberOrNull; + load: NumberOrNull; + uptime: NumberOrNull; + rx: NumberOrNull; + tx: NumberOrNull; + timeseries: Array<{ + timestamp: number; + cpu: NumberOrNull; + iowait: NumberOrNull; + load: NumberOrNull; + rx: NumberOrNull; + tx: NumberOrNull; + }>; +} + export interface MetricsFetchDataResponse extends FetchDataResponse { - stats: { - hosts: Stat; - cpu: Stat; - memory: Stat; - }; + sort: (by: string, direction: string) => Promise; + series: MetricsFetchDataSeries[]; } export interface UptimeFetchDataResponse extends FetchDataResponse { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2496b9ae4ae45..bfc56d551ccc9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -16651,9 +16651,6 @@ "xpack.observability.overview.logs.subtitle": "毎分のログレート", "xpack.observability.overview.logs.title": "ログ", "xpack.observability.overview.metrics.appLink": "アプリで表示", - "xpack.observability.overview.metrics.cpuUsage": "CPU 使用状況", - "xpack.observability.overview.metrics.hosts": "ホスト", - "xpack.observability.overview.metrics.memoryUsage": "メモリー使用状況", "xpack.observability.overview.metrics.title": "メトリック", "xpack.observability.overview.uptime.appLink": "アプリで表示", "xpack.observability.overview.uptime.chart.down": "ダウン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a8d7e6795493c..cf9b558a69994 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -16876,9 +16876,6 @@ "xpack.observability.overview.logs.subtitle": "每分钟日志速率", "xpack.observability.overview.logs.title": "日志", "xpack.observability.overview.metrics.appLink": "在应用中查看", - "xpack.observability.overview.metrics.cpuUsage": "CPU 使用", - "xpack.observability.overview.metrics.hosts": "主机", - "xpack.observability.overview.metrics.memoryUsage": "内存使用", "xpack.observability.overview.metrics.title": "指标", "xpack.observability.overview.uptime.appLink": "在应用中查看", "xpack.observability.overview.uptime.chart.down": "关闭", From d39aa74b050f36e22dba81634c5de1dd4577ad87 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 6 Apr 2021 14:16:18 -0400 Subject: [PATCH 06/20] [ML] Data Frame Analytics: adds functional tests for runtime fields support (#96262) (#96330) * add basic runtime mapping functional tests to analyics * confirm runtime mapping added correctly. --- .../runtime_mappings/runtime_mappings.tsx | 1 + .../runtime_mappings_editor.tsx | 3 +- .../classification_creation.ts | 22 +++++++ .../outlier_detection_creation.ts | 22 +++++++ .../regression_creation.ts | 22 +++++++ .../ml/data_frame_analytics_creation.ts | 64 +++++++++++++++++++ 6 files changed, 133 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx index d9f1d78c302fd..d21bf67a1f51c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx @@ -38,6 +38,7 @@ const COPY_TO_CLIPBOARD_RUNTIME_MAPPINGS = i18n.translate( const { useXJsonMode } = XJson; const xJsonMode = new XJsonMode(); +export type XJsonModeType = ReturnType; interface Props { actions: CreateAnalyticsFormProps['actions']; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx index 70544cc14ba08..66a96e7316e8a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings_editor.tsx @@ -10,6 +10,7 @@ import React, { memo, FC } from 'react'; import { EuiCodeEditor } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isRuntimeMappings } from '../../../../../../../common/util/runtime_field_utils'; +import { XJsonModeType } from './runtime_mappings'; interface Props { convertToJson: (data: string) => string; @@ -17,7 +18,7 @@ interface Props { setIsRuntimeMappingsEditorApplyButtonEnabled: React.Dispatch>; advancedEditorRuntimeMappingsLastApplied: string | undefined; advancedRuntimeMappingsConfig: string; - xJsonMode: any; + xJsonMode: XJsonModeType; } export const RuntimeMappingsEditor: FC = memo( diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index 615b55c6ce56b..5e6a08751c932 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -36,6 +36,12 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.jobId}`; }, + runtimeFields: { + uppercase_y: { + type: 'keyword', + script: 'emit(params._source.y.toUpperCase())', + }, + }, dependentVariable: 'y', trainingPercent: 20, modelMemory: '60mb', @@ -95,6 +101,22 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); + await ml.testExecution.logTestStep('displays the runtime mappings editor switch'); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingSwitchExists(); + + await ml.testExecution.logTestStep('enables the runtime mappings editor'); + await ml.dataFrameAnalyticsCreation.toggleRuntimeMappingsEditorSwitch(true); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent(['']); + + await ml.testExecution.logTestStep('sets runtime mappings'); + await ml.dataFrameAnalyticsCreation.setRuntimeMappingsEditorContent( + JSON.stringify(testData.runtimeFields) + ); + await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ + '{"uppercase_y":{"type":"keyword","script":"emit(params._source.y.toUpperCase())"}}', + ]); + await ml.testExecution.logTestStep('inputs the dependent variable'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputExists(); await ml.dataFrameAnalyticsCreation.selectDependentVariable(testData.dependentVariable); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index d72ee4fa0fd24..e73a477d21b1b 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -35,6 +35,12 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.jobId}`; }, + runtimeFields: { + lowercase_central_air: { + type: 'keyword', + script: 'emit(params._source.CentralAir.toLowerCase())', + }, + }, modelMemory: '5mb', createIndexPattern: true, expected: { @@ -106,6 +112,22 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); + await ml.testExecution.logTestStep('displays the runtime mappings editor switch'); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingSwitchExists(); + + await ml.testExecution.logTestStep('enables the runtime mappings editor'); + await ml.dataFrameAnalyticsCreation.toggleRuntimeMappingsEditorSwitch(true); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent(['']); + + await ml.testExecution.logTestStep('sets runtime mappings'); + await ml.dataFrameAnalyticsCreation.setRuntimeMappingsEditorContent( + JSON.stringify(testData.runtimeFields) + ); + await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ + '{"lowercase_central_air":{"type":"keyword","script":"emit(params._source.CentralAir.toLowerCase())"}}', + ]); + await ml.testExecution.logTestStep('does not display the dependent variable input'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputMissing(); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index 8e28a9933cda0..540fbc10fa0fc 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -35,6 +35,12 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.jobId}`; }, + runtimeFields: { + uppercase_stab: { + type: 'keyword', + script: 'emit(params._source.stabf.toUpperCase())', + }, + }, dependentVariable: 'stab', trainingPercent: 20, modelMemory: '20mb', @@ -89,6 +95,22 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataFrameAnalyticsCreation.assertJobTypeSelectExists(); await ml.dataFrameAnalyticsCreation.selectJobType(testData.jobType); + await ml.testExecution.logTestStep('displays the runtime mappings editor switch'); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingSwitchExists(); + + await ml.testExecution.logTestStep('enables the runtime mappings editor'); + await ml.dataFrameAnalyticsCreation.toggleRuntimeMappingsEditorSwitch(true); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent(['']); + + await ml.testExecution.logTestStep('sets runtime mappings'); + await ml.dataFrameAnalyticsCreation.setRuntimeMappingsEditorContent( + JSON.stringify(testData.runtimeFields) + ); + await ml.dataFrameAnalyticsCreation.applyRuntimeMappings(); + await ml.dataFrameAnalyticsCreation.assertRuntimeMappingsEditorContent([ + '{"uppercase_stab":{"type":"keyword","script":"emit(params._source.stabf.toUpperCase())"}}', + ]); + await ml.testExecution.logTestStep('inputs the dependent variable'); await ml.dataFrameAnalyticsCreation.assertDependentVariableInputExists(); await ml.dataFrameAnalyticsCreation.selectDependentVariable(testData.dependentVariable); diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index 23bdc17919a7b..b22748608589e 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -25,6 +25,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( const testSubjects = getService('testSubjects'); const comboBox = getService('comboBox'); const retry = getService('retry'); + const aceEditor = getService('aceEditor'); return { async assertJobTypeSelectExists() { @@ -237,6 +238,69 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( ); }, + async assertRuntimeMappingSwitchExists() { + await testSubjects.existOrFail('mlDataFrameAnalyticsRuntimeMappingsEditorSwitch'); + }, + + async assertRuntimeMappingEditorExists() { + await testSubjects.existOrFail('mlDataFrameAnalyticsAdvancedRuntimeMappingsEditor'); + }, + + async assertRuntimeMappingsEditorSwitchCheckState(expectedCheckState: boolean) { + const actualCheckState = await this.getRuntimeMappingsEditorSwitchCheckedState(); + expect(actualCheckState).to.eql( + expectedCheckState, + `Advanced runtime mappings editor switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` + ); + }, + + async getRuntimeMappingsEditorSwitchCheckedState(): Promise { + const subj = 'mlDataFrameAnalyticsRuntimeMappingsEditorSwitch'; + const isSelected = await testSubjects.getAttribute(subj, 'aria-checked'); + return isSelected === 'true'; + }, + + async toggleRuntimeMappingsEditorSwitch(toggle: boolean) { + const subj = 'mlDataFrameAnalyticsRuntimeMappingsEditorSwitch'; + if ((await this.getRuntimeMappingsEditorSwitchCheckedState()) !== toggle) { + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.clickWhenNotDisabled(subj); + await this.assertRuntimeMappingsEditorSwitchCheckState(toggle); + }); + } + }, + + async setRuntimeMappingsEditorContent(input: string) { + await aceEditor.setValue('mlDataFrameAnalyticsAdvancedRuntimeMappingsEditor', input); + }, + + async assertRuntimeMappingsEditorContent(expectedContent: string[]) { + await this.assertRuntimeMappingEditorExists(); + + const runtimeMappingsEditorString = await aceEditor.getValue( + 'mlDataFrameAnalyticsAdvancedRuntimeMappingsEditor' + ); + // Not all lines may be visible in the editor and thus aceEditor may not return all lines. + // This means we might not get back valid JSON so we only test against the first few lines + // and see if the string matches. + const splicedAdvancedEditorValue = runtimeMappingsEditorString.split('\n').splice(0, 3); + expect(splicedAdvancedEditorValue).to.eql( + expectedContent, + `Expected the first editor lines to be '${expectedContent}' (got '${splicedAdvancedEditorValue}')` + ); + }, + + async applyRuntimeMappings() { + const subj = 'mlDataFrameAnalyticsRuntimeMappingsApplyButton'; + await testSubjects.existOrFail(subj); + await testSubjects.clickWhenNotDisabled(subj); + const isEnabled = await testSubjects.isEnabled(subj); + expect(isEnabled).to.eql( + false, + `Expected runtime mappings 'Apply changes' button to be disabled, got enabled.` + ); + }, + async assertDependentVariableSelection(expectedSelection: string[]) { await this.waitForDependentVariableInputLoaded(); const actualSelection = await comboBox.getComboBoxSelectedOptions( From 7f9ca8f732d5afbd22e580666c77ce1102222903 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 6 Apr 2021 14:29:45 -0400 Subject: [PATCH 07/20] Bump cypress@6.8.0 (#95041) (#96333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Patryk Kopyciński --- package.json | 4 ++-- yarn.lock | 54 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index e79fdb65a28b0..a0633c9740087 100644 --- a/package.json +++ b/package.json @@ -441,7 +441,7 @@ "@bazel/ibazel": "^0.14.0", "@bazel/typescript": "^3.2.3", "@cypress/snapshot": "^2.1.7", - "@cypress/webpack-preprocessor": "^5.5.0", + "@cypress/webpack-preprocessor": "^5.6.0", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", "@elastic/eslint-config-kibana": "link:packages/elastic-eslint-config-kibana", @@ -679,7 +679,7 @@ "copy-webpack-plugin": "^6.0.2", "cpy": "^8.1.1", "css-loader": "^3.4.2", - "cypress": "^6.2.1", + "cypress": "^6.8.0", "cypress-cucumber-preprocessor": "^2.5.2", "cypress-multi-reporters": "^1.4.0", "cypress-pipe": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 522ae93a72e10..1b5d345aa6892 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1294,13 +1294,13 @@ snap-shot-compare "2.8.3" snap-shot-store "1.2.3" -"@cypress/webpack-preprocessor@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.5.0.tgz#e7010b2ee7449691cc16a9d5d1956af17ea175fd" - integrity sha512-iqwPygSNZ1u6bM3r5QRVv6qYngkcgI2xCzi9Jmo4mrkcofwX08UaItJq7xlB2/dHbB2aryQYOsfe4xNKtQIm3A== +"@cypress/webpack-preprocessor@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.6.0.tgz#9648ae22d2e52f17a604e2a493af27a9c96568bd" + integrity sha512-kSelTDe6gs3Skp4vPP2vfTvAl+Ua+9rR/AMTir7bgJihDvzFESqnjWtF6N1TrPo+vCFVGx0VUA6JUvDkhvpwhA== dependencies: bluebird "^3.7.1" - debug "^4.1.1" + debug "4.3.2" lodash "^4.17.20" "@cypress/xvfb@^1.2.4": @@ -5390,7 +5390,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@14.14.14", "@types/node@8.10.54", "@types/node@>= 8", "@types/node@>=8.9.0", "@types/node@^10.1.0", "@types/node@^12.0.2": +"@types/node@*", "@types/node@12.12.50", "@types/node@14.14.14", "@types/node@8.10.54", "@types/node@>= 8", "@types/node@>=8.9.0", "@types/node@^10.1.0", "@types/node@^12.0.2": version "14.14.14" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae" integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ== @@ -11004,14 +11004,15 @@ cypress-promise@^1.1.0: resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25" integrity sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA== -cypress@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.2.1.tgz#27d5fbcf008c698c390fdb0c03441804176d06c4" - integrity sha512-OYkSgzA4J4Q7eMjZvNf5qWpBLR4RXrkqjL3UZ1UzGGLAskO0nFTi/RomNTG6TKvL3Zp4tw4zFY1gp5MtmkCZrA== +cypress@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.8.0.tgz#8338f39212a8f71e91ff8c017a1b6e22d823d8c1" + integrity sha512-W2e9Oqi7DmF48QtOD0LfsOLVq6ef2hcXZvJXI/E3PgFNmZXEVwBefhAxVCW9yTPortjYA2XkM20KyC4HRkOm9w== dependencies: "@cypress/listr-verbose-renderer" "^0.4.1" "@cypress/request" "^2.88.5" "@cypress/xvfb" "^1.2.4" + "@types/node" "12.12.50" "@types/sinonjs__fake-timers" "^6.0.1" "@types/sizzle" "^2.3.2" arch "^2.1.2" @@ -11023,7 +11024,8 @@ cypress@^6.2.1: cli-table3 "~0.6.0" commander "^5.1.0" common-tags "^1.8.0" - debug "^4.1.1" + dayjs "^1.9.3" + debug "4.3.2" eventemitter2 "^6.4.2" execa "^4.0.2" executable "^4.1.1" @@ -11037,10 +11039,10 @@ cypress@^6.2.1: lodash "^4.17.19" log-symbols "^4.0.0" minimist "^1.2.5" - moment "^2.27.0" + moment "^2.29.1" ospath "^1.2.2" pretty-bytes "^5.4.1" - ramda "~0.26.1" + ramda "~0.27.1" request-progress "^3.0.0" supports-color "^7.2.0" tmp "~0.2.1" @@ -11407,6 +11409,11 @@ dateformat@^3.0.2, dateformat@~3.0.3: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.9.3: + version "1.10.4" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" + integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== + debug-fabulous@1.X: version "1.1.0" resolved "https://registry.yarnpkg.com/debug-fabulous/-/debug-fabulous-1.1.0.tgz#af8a08632465224ef4174a9f06308c3c2a1ebc8e" @@ -11465,6 +11472,13 @@ debug@4.2.0: dependencies: ms "2.1.2" +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -20254,11 +20268,16 @@ moment-timezone@^0.5.27: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.19.3, moment@^2.24.0, moment@^2.27.0: +"moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.19.3, moment@^2.24.0: version "2.28.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.28.0.tgz#cdfe73ce01327cee6537b0fafac2e0f21a237d75" integrity sha512-Z5KOjYmnHyd/ukynmFd/WwyXHd7L4J9vTI/nn5Ap9AVUgaAE15VvQ9MOGmJJygEUklupqIrFnor/tjTwRU+tQw== +moment@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + monaco-editor@*, monaco-editor@^0.22.3: version "0.22.3" resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.22.3.tgz#69b42451d3116c6c08d9b8e052007ff891fd85d7" @@ -23229,11 +23248,16 @@ ramda@^0.21.0: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU= -ramda@^0.26, ramda@^0.26.1, ramda@~0.26.1: +ramda@^0.26, ramda@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== +ramda@~0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== + randexp@0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" From 853c2ed57dfe37564028894532593ea05ba5967b Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 6 Apr 2021 20:53:42 +0200 Subject: [PATCH 08/20] notify main dev process when server is ready (#96332) (#96334) * notify main dev process when server is ready * check for process.send existence --- src/core/server/bootstrap.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/server/bootstrap.ts b/src/core/server/bootstrap.ts index 4a07e0c010685..a2267635e86f2 100644 --- a/src/core/server/bootstrap.ts +++ b/src/core/server/bootstrap.ts @@ -83,6 +83,11 @@ export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: Boot try { await root.setup(); await root.start(); + + // notify parent process know when we are ready for dev mode. + if (process.send) { + process.send(['SERVER_LISTENING']); + } } catch (err) { await shutdown(err); } From 2ebcddbb3b04acd18b655e49a4cc07aeeab84840 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 6 Apr 2021 12:05:56 -0700 Subject: [PATCH 09/20] Fix autocomplete telemetry (#95724) (#96218) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ui/query_string_input/query_string_input.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 71ff09e81c567..16e1325b2b56b 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -379,14 +379,12 @@ export default class QueryStringInputUI extends Component { const newQueryString = value.substr(0, start) + text + value.substr(end); this.reportUiCounter?.( - METRIC_TYPE.LOADED, - `query_string:${type}:suggestions_select_position`, - listIndex + METRIC_TYPE.CLICK, + `query_string:${type}:suggestions_select_position_${listIndex}` ); this.reportUiCounter?.( - METRIC_TYPE.LOADED, - `query_string:${type}:suggestions_select_q_length`, - end - start + METRIC_TYPE.CLICK, + `query_string:${type}:suggestions_select_q_length_${end - start}` ); this.onQueryStringChange(newQueryString); From 21978b6a44bc8307e3dbcd8544754fc78cc5f921 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 6 Apr 2021 12:45:21 -0700 Subject: [PATCH 10/20] skip flaky suite (#89477) --- test/functional/apps/discover/_saved_queries.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts index 88b3af50ac9dd..61b68ca6bb40a 100644 --- a/test/functional/apps/discover/_saved_queries.ts +++ b/test/functional/apps/discover/_saved_queries.ts @@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const savedQueryManagementComponent = getService('savedQueryManagementComponent'); const testSubjects = getService('testSubjects'); - describe('saved queries saved objects', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/89477 + describe.skip('saved queries saved objects', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); await esArchiver.load('discover'); From 671f91fa2abf1e1f09a3b67464905ce9db8d1d0c Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 6 Apr 2021 12:49:33 -0700 Subject: [PATCH 11/20] skip flaky suite (#92522) --- test/functional/apps/dashboard/dashboard_filtering.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/dashboard/dashboard_filtering.ts b/test/functional/apps/dashboard/dashboard_filtering.ts index e995bc4e52c49..86c57efec818b 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.ts +++ b/test/functional/apps/dashboard/dashboard_filtering.ts @@ -28,7 +28,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardPanelActions = getService('dashboardPanelActions'); const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize', 'timePicker']); - describe('dashboard filtering', function () { + // Failing: See https://github.com/elastic/kibana/issues/92522 + describe.skip('dashboard filtering', function () { this.tags('includeFirefox'); const populateDashboard = async () => { From e5eac06877e7fe3be0bffd4c94ffab71923149c1 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 6 Apr 2021 22:46:56 +0200 Subject: [PATCH 12/20] migration v2 respects the config.batchSize value (#96207) (#96331) * migrationsv2: read batchSize from config * update tests * update numeric values in so config to improve reading * fix integration tests. failed with illegal_argument_exception Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../migrationsv2/actions/index.test.ts | 30 ++++- .../migrationsv2/actions/index.ts | 30 +++-- .../integration_tests/actions.test.ts | 123 +++++++++--------- .../migrations_state_action_machine.test.ts | 4 + .../saved_objects/migrationsv2/model.test.ts | 3 + .../saved_objects/migrationsv2/model.ts | 1 + .../server/saved_objects/migrationsv2/next.ts | 6 +- .../saved_objects/migrationsv2/types.ts | 15 +++ .../saved_objects/saved_objects_config.ts | 4 +- 9 files changed, 140 insertions(+), 76 deletions(-) diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts index a21078cbe1135..14ca73e7fcca0 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts @@ -163,7 +163,12 @@ describe('actions', () => { describe('searchForOutdatedDocuments', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.searchForOutdatedDocuments(client, 'new_index', { properties: {} }); + const task = Actions.searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'new_index', + outdatedDocumentsQuery: {}, + }); + try { await task(); } catch (e) { @@ -172,6 +177,29 @@ describe('actions', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); + + it('configures request according to given parameters', async () => { + const esClient = elasticsearchClientMock.createInternalClient(); + const query = {}; + const targetIndex = 'new_index'; + const batchSize = 1000; + const task = Actions.searchForOutdatedDocuments(esClient, { + batchSize, + targetIndex, + outdatedDocumentsQuery: query, + }); + + await task(); + + expect(esClient.search).toHaveBeenCalledTimes(1); + expect(esClient.search).toHaveBeenCalledWith( + expect.objectContaining({ + index: targetIndex, + size: batchSize, + body: expect.objectContaining({ query }), + }) + ); + }); }); describe('bulkOverwriteTransformedDocuments', () => { diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index 52fa99b724873..8ac683a29d657 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -9,11 +9,11 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; -import { ElasticsearchClientError, ResponseError } from '@elastic/elasticsearch/lib/errors'; -import { pipe } from 'fp-ts/lib/pipeable'; +import type { estypes } from '@elastic/elasticsearch'; import { errors as EsErrors } from '@elastic/elasticsearch'; +import type { ElasticsearchClientError, ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { pipe } from 'fp-ts/lib/pipeable'; import { flow } from 'fp-ts/lib/function'; -import type { estypes } from '@elastic/elasticsearch'; import { ElasticsearchClient } from '../../../elasticsearch'; import { IndexMapping } from '../../mappings'; import { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '../../serialization'; @@ -24,13 +24,10 @@ import { export type { RetryableEsClientError }; /** - * Batch size for updateByQuery, reindex & search operations. Smaller batches - * reduce the memory pressure on Elasticsearch and Kibana so are less likely - * to cause failures. - * TODO (profile/tune): How much smaller can we make this number before it - * starts impacting how long migrations take to perform? + * Batch size for updateByQuery and reindex operations. + * Uses the default value of 1000 for Elasticsearch reindex operation. */ -const BATCH_SIZE = 1000; +const BATCH_SIZE = 1_000; const DEFAULT_TIMEOUT = '60s'; /** Allocate 1 replica if there are enough data nodes, otherwise continue with 0 */ const INDEX_AUTO_EXPAND_REPLICAS = '0-1'; @@ -839,6 +836,12 @@ export interface SearchResponse { outdatedDocuments: SavedObjectsRawDoc[]; } +interface SearchForOutdatedDocumentsOptions { + batchSize: number; + targetIndex: string; + outdatedDocumentsQuery?: estypes.QueryContainer; +} + /** * Search for outdated saved object documents with the provided query. Will * return one batch of documents. Searching should be repeated until no more @@ -846,18 +849,17 @@ export interface SearchResponse { */ export const searchForOutdatedDocuments = ( client: ElasticsearchClient, - index: string, - query: Record + options: SearchForOutdatedDocumentsOptions ): TaskEither.TaskEither => () => { return client .search({ - index, + index: options.targetIndex, // Return the _seq_no and _primary_term so we can use optimistic // concurrency control for updates seq_no_primary_term: true, - size: BATCH_SIZE, + size: options.batchSize, body: { - query, + query: options.outdatedDocumentsQuery, // Optimize search performance by sorting by the "natural" index order sort: ['_doc'], }, diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 1824efa0ed8d4..aa9a5ea92ac11 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -59,7 +59,7 @@ describe('migration actions', () => { // Create test fixture data: await createIndex(client, 'existing_index_with_docs', { - dynamic: true as any, + dynamic: true, properties: {}, })(); const sourceDocs = ([ @@ -337,7 +337,6 @@ describe('migration actions', () => { // Reindex doesn't return any errors on it's own, so we have to test // together with waitForReindexTask describe('reindex & waitForReindexTask', () => { - expect.assertions(2); it('resolves right when reindex succeeds without reindex script', async () => { const res = (await reindex( client, @@ -354,11 +353,11 @@ describe('migration actions', () => { } `); - const results = ((await searchForOutdatedDocuments( - client, - 'reindex_target', - undefined as any - )()) as Either.Right).right.outdatedDocuments; + const results = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'reindex_target', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments; expect(results.map((doc) => doc._source.title)).toMatchInlineSnapshot(` Array [ "doc 1", @@ -384,11 +383,11 @@ describe('migration actions', () => { "right": "reindex_succeeded", } `); - const results = ((await searchForOutdatedDocuments( - client, - 'reindex_target_2', - undefined as any - )()) as Either.Right).right.outdatedDocuments; + const results = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'reindex_target_2', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments; expect(results.map((doc) => doc._source.title)).toMatchInlineSnapshot(` Array [ "doc 1_updated", @@ -432,12 +431,12 @@ describe('migration actions', () => { } `); - // Assert that documents weren't overrided by the second, unscripted reindex - const results = ((await searchForOutdatedDocuments( - client, - 'reindex_target_3', - undefined as any - )()) as Either.Right).right.outdatedDocuments; + // Assert that documents weren't overridden by the second, unscripted reindex + const results = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'reindex_target_3', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments; expect(results.map((doc) => doc._source.title)).toMatchInlineSnapshot(` Array [ "doc 1_updated", @@ -452,11 +451,11 @@ describe('migration actions', () => { // Simulate a reindex that only adds some of the documents from the // source index into the target index await createIndex(client, 'reindex_target_4', { properties: {} })(); - const sourceDocs = ((await searchForOutdatedDocuments( - client, - 'existing_index_with_docs', - undefined as any - )()) as Either.Right).right.outdatedDocuments + const sourceDocs = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'existing_index_with_docs', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments .slice(0, 2) .map(({ _id, _source }) => ({ _id, @@ -479,13 +478,13 @@ describe('migration actions', () => { "right": "reindex_succeeded", } `); - // Assert that existing documents weren't overrided, but that missing + // Assert that existing documents weren't overridden, but that missing // documents were added by the reindex - const results = ((await searchForOutdatedDocuments( - client, - 'reindex_target_4', - undefined as any - )()) as Either.Right).right.outdatedDocuments; + const results = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'reindex_target_4', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments; expect(results.map((doc) => doc._source.title)).toMatchInlineSnapshot(` Array [ "doc 1", @@ -701,26 +700,30 @@ describe('migration actions', () => { describe('searchForOutdatedDocuments', () => { it('only returns documents that match the outdatedDocumentsQuery', async () => { expect.assertions(2); - const resultsWithQuery = ((await searchForOutdatedDocuments( - client, - 'existing_index_with_docs', - { + const resultsWithQuery = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'existing_index_with_docs', + outdatedDocumentsQuery: { match: { title: { query: 'doc' } }, - } - )()) as Either.Right).right.outdatedDocuments; + }, + })()) as Either.Right).right.outdatedDocuments; expect(resultsWithQuery.length).toBe(3); - const resultsWithoutQuery = ((await searchForOutdatedDocuments( - client, - 'existing_index_with_docs', - undefined as any - )()) as Either.Right).right.outdatedDocuments; + const resultsWithoutQuery = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'existing_index_with_docs', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments; expect(resultsWithoutQuery.length).toBe(4); }); it('resolves with _id, _source, _seq_no and _primary_term', async () => { expect.assertions(1); - const results = ((await searchForOutdatedDocuments(client, 'existing_index_with_docs', { - match: { title: { query: 'doc' } }, + const results = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'existing_index_with_docs', + outdatedDocumentsQuery: { + match: { title: { query: 'doc' } }, + }, })()) as Either.Right).right.outdatedDocuments; expect(results).toEqual( expect.arrayContaining([ @@ -805,7 +808,7 @@ describe('migration actions', () => { it('resolves right when mappings were updated and picked up', async () => { // Create an index without any mappings and insert documents into it await createIndex(client, 'existing_index_without_mappings', { - dynamic: false as any, + dynamic: false, properties: {}, })(); const sourceDocs = ([ @@ -821,11 +824,13 @@ describe('migration actions', () => { )(); // Assert that we can't search over the unmapped fields of the document - const originalSearchResults = ((await searchForOutdatedDocuments( - client, - 'existing_index_without_mappings', - { match: { title: { query: 'doc' } } } - )()) as Either.Right).right.outdatedDocuments; + const originalSearchResults = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'existing_index_without_mappings', + outdatedDocumentsQuery: { + match: { title: { query: 'doc' } }, + }, + })()) as Either.Right).right.outdatedDocuments; expect(originalSearchResults.length).toBe(0); // Update and pickup mappings so that the title field is searchable @@ -839,11 +844,13 @@ describe('migration actions', () => { await waitForPickupUpdatedMappingsTask(client, taskId, '60s')(); // Repeat the search expecting to be able to find the existing documents - const pickedUpSearchResults = ((await searchForOutdatedDocuments( - client, - 'existing_index_without_mappings', - { match: { title: { query: 'doc' } } } - )()) as Either.Right).right.outdatedDocuments; + const pickedUpSearchResults = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'existing_index_without_mappings', + outdatedDocumentsQuery: { + match: { title: { query: 'doc' } }, + }, + })()) as Either.Right).right.outdatedDocuments; expect(pickedUpSearchResults.length).toBe(4); }); }); @@ -1050,11 +1057,11 @@ describe('migration actions', () => { `); }); it('resolves right even if there were some version_conflict_engine_exception', async () => { - const existingDocs = ((await searchForOutdatedDocuments( - client, - 'existing_index_with_docs', - undefined as any - )()) as Either.Right).right.outdatedDocuments; + const existingDocs = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'existing_index_with_docs', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments; const task = bulkOverwriteTransformedDocuments(client, 'existing_index_with_docs', [ ...existingDocs, diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts index 99c06c0a3586b..d4ce7b74baa5f 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts @@ -206,6 +206,7 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] INIT -> LEGACY_DELETE", Object { + "batchSize": 1000, "controlState": "LEGACY_DELETE", "currentAlias": ".my-so-index", "indexPrefix": ".my-so-index", @@ -262,6 +263,7 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] LEGACY_DELETE -> FATAL", Object { + "batchSize": 1000, "controlState": "FATAL", "currentAlias": ".my-so-index", "indexPrefix": ".my-so-index", @@ -413,6 +415,7 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] INIT -> LEGACY_REINDEX", Object { + "batchSize": 1000, "controlState": "LEGACY_REINDEX", "currentAlias": ".my-so-index", "indexPrefix": ".my-so-index", @@ -464,6 +467,7 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE", Object { + "batchSize": 1000, "controlState": "LEGACY_DELETE", "currentAlias": ".my-so-index", "indexPrefix": ".my-so-index", diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index 2813f01093e95..f9bf3418c0ab6 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -46,6 +46,7 @@ describe('migrations v2 model', () => { retryCount: 0, retryDelay: 0, retryAttempts: 15, + batchSize: 1000, indexPrefix: '.kibana', outdatedDocumentsQuery: {}, targetIndexMappings: { @@ -1182,6 +1183,7 @@ describe('migrations v2 model', () => { describe('createInitialState', () => { const migrationsConfig = ({ retryAttempts: 15, + batchSize: 1000, } as unknown) as SavedObjectsMigrationConfigType; it('creates the initial state for the model based on the passed in paramaters', () => { expect( @@ -1197,6 +1199,7 @@ describe('migrations v2 model', () => { }) ).toMatchInlineSnapshot(` Object { + "batchSize": 1000, "controlState": "INIT", "currentAlias": ".kibana_task_manager", "indexPrefix": ".kibana_task_manager", diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index 5bdba98026792..e62bd108faea0 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -784,6 +784,7 @@ export const createInitialState = ({ retryCount: 0, retryDelay: 0, retryAttempts: migrationsConfig.retryAttempts, + batchSize: migrationsConfig.batchSize, logs: [], }; return initialState; diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts index 1b594cf3d8b53..5c159f4f24e22 100644 --- a/src/core/server/saved_objects/migrationsv2/next.ts +++ b/src/core/server/saved_objects/migrationsv2/next.ts @@ -73,7 +73,11 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK: (state: UpdateTargetMappingsWaitForTaskState) => Actions.waitForPickupUpdatedMappingsTask(client, state.updateTargetMappingsTaskId, '60s'), OUTDATED_DOCUMENTS_SEARCH: (state: OutdatedDocumentsSearch) => - Actions.searchForOutdatedDocuments(client, state.targetIndex, state.outdatedDocumentsQuery), + Actions.searchForOutdatedDocuments(client, { + batchSize: state.batchSize, + targetIndex: state.targetIndex, + outdatedDocumentsQuery: state.outdatedDocumentsQuery, + }), OUTDATED_DOCUMENTS_TRANSFORM: (state: OutdatedDocumentsTransform) => pipe( TaskEither.tryCatch( diff --git a/src/core/server/saved_objects/migrationsv2/types.ts b/src/core/server/saved_objects/migrationsv2/types.ts index dbdd5774dfa62..8d6fe3f030eb3 100644 --- a/src/core/server/saved_objects/migrationsv2/types.ts +++ b/src/core/server/saved_objects/migrationsv2/types.ts @@ -54,6 +54,21 @@ export interface BaseState extends ControlState { * max_retry_time = 11.7 minutes */ readonly retryAttempts: number; + + /** + * The number of documents to fetch from Elasticsearch server to run migration over. + * + * The higher the value, the faster the migration process will be performed since it reduces + * the number of round trips between Kibana and Elasticsearch servers. + * For the migration speed, we have to pay the price of increased memory consumption. + * + * Since batchSize defines the number of documents, not their size, it might happen that + * Elasticsearch fails a request with circuit_breaking_exception when it retrieves a set of + * saved objects of significant size. + * + * In this case, you should set a smaller batchSize value and restart the migration process again. + */ + readonly batchSize: number; readonly logs: Array<{ level: 'error' | 'info'; message: string }>; /** * The current alias e.g. `.kibana` which always points to the latest diff --git a/src/core/server/saved_objects/saved_objects_config.ts b/src/core/server/saved_objects/saved_objects_config.ts index 7228cb126d286..96fac85ded076 100644 --- a/src/core/server/saved_objects/saved_objects_config.ts +++ b/src/core/server/saved_objects/saved_objects_config.ts @@ -29,8 +29,8 @@ export type SavedObjectsConfigType = TypeOf; export const savedObjectsConfig = { path: 'savedObjects', schema: schema.object({ - maxImportPayloadBytes: schema.byteSize({ defaultValue: 26214400 }), - maxImportExportSize: schema.number({ defaultValue: 10000 }), + maxImportPayloadBytes: schema.byteSize({ defaultValue: 26_214_400 }), + maxImportExportSize: schema.number({ defaultValue: 10_000 }), }), }; From bb06fc2f3fe001650c013a1fce16afaa216dc2a5 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 6 Apr 2021 17:25:42 -0400 Subject: [PATCH 13/20] Fix reauthenticate links for OneDrive and SharePoint (#96271) (#96347) For both OneDrive and SharePoint we define a service_type that has an underscore in the middle (one_drive and share_point). This fixes the route definitions so sources of these connector types can be reauthenticated. Co-authored-by: James Rucker --- .../public/applications/workplace_search/routes.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 50f6596a860c5..9e514d7c73493 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -73,11 +73,11 @@ export const ADD_GMAIL_PATH = `${SOURCES_PATH}/add/gmail`; export const ADD_GOOGLE_DRIVE_PATH = `${SOURCES_PATH}/add/google_drive`; export const ADD_JIRA_PATH = `${SOURCES_PATH}/add/jira_cloud`; export const ADD_JIRA_SERVER_PATH = `${SOURCES_PATH}/add/jira_server`; -export const ADD_ONEDRIVE_PATH = `${SOURCES_PATH}/add/onedrive`; +export const ADD_ONEDRIVE_PATH = `${SOURCES_PATH}/add/one_drive`; export const ADD_SALESFORCE_PATH = `${SOURCES_PATH}/add/salesforce`; export const ADD_SALESFORCE_SANDBOX_PATH = `${SOURCES_PATH}/add/salesforce_sandbox`; export const ADD_SERVICENOW_PATH = `${SOURCES_PATH}/add/servicenow`; -export const ADD_SHAREPOINT_PATH = `${SOURCES_PATH}/add/sharepoint`; +export const ADD_SHAREPOINT_PATH = `${SOURCES_PATH}/add/share_point`; export const ADD_SLACK_PATH = `${SOURCES_PATH}/add/slack`; export const ADD_ZENDESK_PATH = `${SOURCES_PATH}/add/zendesk`; export const ADD_CUSTOM_PATH = `${SOURCES_PATH}/add/custom`; @@ -108,11 +108,11 @@ export const EDIT_GMAIL_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/gmail/edit`; export const EDIT_GOOGLE_DRIVE_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/google_drive/edit`; export const EDIT_JIRA_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/jira_cloud/edit`; export const EDIT_JIRA_SERVER_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/jira_server/edit`; -export const EDIT_ONEDRIVE_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/onedrive/edit`; +export const EDIT_ONEDRIVE_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/one_drive/edit`; export const EDIT_SALESFORCE_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/salesforce/edit`; export const EDIT_SALESFORCE_SANDBOX_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/salesforce_sandbox/edit`; export const EDIT_SERVICENOW_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/servicenow/edit`; -export const EDIT_SHAREPOINT_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/sharepoint/edit`; +export const EDIT_SHAREPOINT_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/share_point/edit`; export const EDIT_SLACK_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/slack/edit`; export const EDIT_ZENDESK_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/zendesk/edit`; export const EDIT_CUSTOM_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/custom/edit`; From 1d458859f1cbecbf16d76e630c94eb72c52a678b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 6 Apr 2021 18:18:29 -0400 Subject: [PATCH 14/20] [Fleet] Can't select managed agent. Bulk upgrade agent UI changes (#96087) (#96312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR implements several items from point 8 in https://github.com/elastic/observability-design/issues/32 The changes to modal titles and success/error behavior are only for bulk upgrade in this PR. Once we confirm the pattern, I'll apply it to unenroll and reassign in a follow up PR. - [x] Disable the checkbox for agents enrolled in a hosted agent policy. We don't need to provide a tooltip description since the Agent policy "lock" icon appears in the table row. - [x] If a user selects the top-left checkbox to select all agents on the page, and then clicks "select everything on all pages", we don't need to provide a count for total number of agents selected. The bulk actions button can say "all agents selected" (as discussed, calculating the total number of agents is problematic and can be tough on performance). Choosing a bulk action should filter out / not include any agents that are enrolled in a hosted agent policy. - [x] Related to calculating total number of agents, we previously showed a count in the action modal's title. In the case where users have selected everything on all pages, the title can just say "all selected agents" -> i.e. "upgrade all selected agents" - [x] If the result of a bulk action has mixed results, as in some percentage of agents are successful but others fail, show a warning toast that indicates how many succeeded and how many failed. See screenshot below. - [x] Change the "experimental" badge for the upgrade agent action modal to be an icon only badge. You can use the `beaker` icon. The badge should use the same tooltip description that we use today indicating that this action is experimental. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ## Per feature details & screenshots
Disable the checkbox for agents enrolled in a hosted agent policy. Screen Shot 2021-04-02 at 10 35 38 AM
We don't need to provide a tooltip description since the Agent policy "lock" icon appears in the table row.
I left the description in until we implement the "lock" icon.
If a user selects the top-left checkbox to select all agents on the page, and then clicks "select everything on all pages", ... the bulk actions button can say "all agents selected" Screen Shot 2021-04-02 at 9 20 28 AM Screen Shot 2021-04-02 at 9 20 45 AM
Choosing a bulk action should filter out / not include any agents that are enrolled in a hosted agent policy.
Screen Shot 2021-04-02 at 10 58 43 AM Screen Shot 2021-04-02 at 11 00 44 AM Screen Shot 2021-04-02 at 10 59 27 AM There are 7 rows, but only 6 were attempted because 1 is managed
When users have selected everything on all pages, the title can just say "all selected agents" -> i.e. "upgrade all selected agents" Screen Shot 2021-04-01 at 11 35 06 AM the text inside the modal was also updated per the screenshots

Single agent case

Screen Shot 2021-04-01 at 11 35 18 AM

Multiple items but not "all"

Screen Shot 2021-04-01 at 11 35 33 AM
If the result of a bulk action has mixed results, as in some percentage of agents are successful but others fail, show a warning toast that indicates how many succeeded and how many failed

Mixed success & failure: show warning

Screen Shot 2021-04-02 at 10 59 27 AM

All succeed. Variants for multiple vs all selected

Screen Shot 2021-04-02 at 11 26 46 AM Screen Shot 2021-04-02 at 11 59 44 AM

All fail. Variants for multiple vs all selected

Screen Shot 2021-04-02 at 11 14 48 AM Screen Shot 2021-04-02 at 11 19 17 AM
Change the "experimental" badge for the upgrade agent action modal to be an icon only badge. You can use the `beaker` icon. The badge should use the same tooltip description that we use today indicating that this action is experimental. Screen Shot 2021-04-01 at 11 35 06 AM
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: John Schulz --- .../components/bulk_actions.tsx | 34 ++++----- .../sections/agents/agent_list_page/index.tsx | 24 +++++- .../components/agent_upgrade_modal/index.tsx | 73 +++++++++++++++---- .../fleet/server/services/agent_policy.ts | 2 +- .../fleet/server/services/agents/upgrade.ts | 26 +++++-- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 7 files changed, 115 insertions(+), 52 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index c859d585f4d82..de27d5fada755 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -148,12 +148,21 @@ export const AgentBulkActions: React.FunctionComponent<{ }, ]; + const showSelectEverything = + selectionMode === 'manual' && + selectedAgents.length === selectableAgents && + selectableAgents < totalAgents; + + const totalActiveAgents = totalAgents - totalInactiveAgents; + const agentCount = selectionMode === 'manual' ? selectedAgents.length : totalActiveAgents; + const agents = selectionMode === 'manual' ? selectedAgents : currentQuery; + return ( <> {isReassignFlyoutOpen && ( { setIsReassignFlyoutOpen(false); refreshAgents(); @@ -164,10 +173,8 @@ export const AgentBulkActions: React.FunctionComponent<{ {isUnenrollModalOpen && ( { setIsUnenrollModalOpen(false); refreshAgents(); @@ -179,10 +186,8 @@ export const AgentBulkActions: React.FunctionComponent<{ { setIsUpgradeModalOpen(false); refreshAgents(); @@ -230,12 +235,9 @@ export const AgentBulkActions: React.FunctionComponent<{ > @@ -248,9 +250,7 @@ export const AgentBulkActions: React.FunctionComponent<{ - {selectionMode === 'manual' && - selectedAgents.length === selectableAgents && - selectableAgents < totalAgents ? ( + {showSelectEverything ? (