From eab74f400522faf4856b59dedddeea07c2ffe45f Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Tue, 2 Jun 2020 16:08:41 -0700 Subject: [PATCH 01/15] [Ingest Manager] Change agent config YAML view to flyout (#67918) * Consolidate context menu/row action components and usage, and some export/imports * Missed export * Move agent config yaml tab to flyout, add and consolidate agent config action menus * Fix i18n * Add download config YAML endpoint and link from flyout, add common config->yaml service * Actually remove the tab lol * Fix i18n --- .../ingest_manager/common/constants/routes.ts | 1 + .../common/services/config_to_yaml.ts | 39 +++++ .../ingest_manager/common/services/index.ts | 1 + .../ingest_manager/common/services/routes.ts | 7 + .../components/context_menu_actions.tsx | 68 +++++++++ .../ingest_manager/components/index.ts | 3 + .../components/table_row_actions_nested.tsx | 38 ----- .../ingest_manager/constants/page_paths.ts | 1 - .../ingest_manager/hooks/index.ts | 1 + .../hooks/use_request/agent_config.ts | 3 +- .../agent_config/components/actions_menu.tsx | 69 +++++++++ .../components/config_yaml_flyout.tsx | 99 +++++++++++++ .../sections/agent_config/components/index.ts | 4 +- .../components/table_row_actions.tsx | 38 ----- .../datasources/datasources_table.tsx | 10 +- .../details_page/components/index.ts | 2 + .../details_page/components/yaml/index.tsx | 56 -------- .../agent_config/details_page/index.tsx | 38 +++-- .../sections/agent_config/list_page/index.tsx | 68 ++------- .../components/data_stream_row_actions.tsx | 9 +- .../components/actions_menu.tsx | 99 ++++++------- .../sections/fleet/agent_list_page/index.tsx | 135 ++++++++---------- .../ingest_manager/services/index.ts | 1 + .../ingest_manager/types/index.ts | 1 + .../server/routes/agent_config/handlers.ts | 37 ++++- .../server/routes/agent_config/index.ts | 11 ++ .../ingest_manager/server/services/index.ts | 2 +- .../server/types/rest_spec/agent_config.ts | 3 + .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - 30 files changed, 481 insertions(+), 373 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx delete mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/yaml/index.tsx diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index abb266da9f066..3309d8497f4c5 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -46,6 +46,7 @@ export const AGENT_CONFIG_API_ROUTES = { UPDATE_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, DELETE_PATTERN: `${AGENT_CONFIG_API_ROOT}/delete`, FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`, + FULL_INFO_DOWNLOAD_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/download`, }; // Output API routes diff --git a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts new file mode 100644 index 0000000000000..9dfd76b9ddd21 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { safeDump } from 'js-yaml'; +import { FullAgentConfig } from '../types'; + +const CONFIG_KEYS_ORDER = [ + 'id', + 'name', + 'revision', + 'type', + 'outputs', + 'settings', + 'datasources', + 'enabled', + 'package', + 'input', +]; + +export const configToYaml = (config: FullAgentConfig): string => { + return safeDump(config, { + skipInvalid: true, + sortKeys: (keyA: string, keyB: string) => { + const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); + const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); + if (indexA >= 0 && indexB < 0) { + return -1; + } + + if (indexA < 0 && indexB >= 0) { + return 1; + } + + return indexA - indexB; + }, + }); +}; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index 91dbbdd515c3e..c595c9a52f66f 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -8,5 +8,6 @@ import * as AgentStatusKueryHelper from './agent_status'; export * from './routes'; export { packageToConfigDatasourceInputs, packageToConfigDatasource } from './package_to_config'; export { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource'; +export { configToYaml } from './config_to_yaml'; export { AgentStatusKueryHelper }; export { decodeCloudId } from './decode_cloud_id'; diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index 20d040ac6eaee..3fc990ea9d70c 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -90,6 +90,13 @@ export const agentConfigRouteService = { getInfoFullPath: (agentConfigId: string) => { return AGENT_CONFIG_API_ROUTES.FULL_INFO_PATTERN.replace('{agentConfigId}', agentConfigId); }, + + getInfoFullDownloadPath: (agentConfigId: string) => { + return AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN.replace( + '{agentConfigId}', + agentConfigId + ); + }, }; export const dataStreamRouteService = { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx new file mode 100644 index 0000000000000..8a9f0553895a1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanel, + EuiPopover, + EuiButton, +} from '@elastic/eui'; +import { EuiButtonProps } from '@elastic/eui/src/components/button/button'; +import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; +import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; + +type Props = { + button?: { + props: EuiButtonProps; + children: JSX.Element; + }; +} & ( + | { + items: EuiContextMenuPanelProps['items']; + } + | { + panels: EuiContextMenuProps['panels']; + } +); + +export const ContextMenuActions = React.memo(({ button, ...props }) => { + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + + return ( + + {button.children} + + ) : ( + + ) + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + {'items' in props ? ( + + ) : ( + + )} + + ); +}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts index b0b4e79cece79..93bc0645c7eee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts @@ -7,4 +7,7 @@ export { Loading } from './loading'; export { Error } from './error'; export { Header, HeaderProps } from './header'; export { AlphaMessaging } from './alpha_messaging'; +export { PackageIcon } from './package_icon'; +export { ContextMenuActions } from './context_menu_actions'; +export { SearchBar } from './search_bar'; export * from './settings_flyout'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx deleted file mode 100644 index 56f010e2fa774..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/table_row_actions_nested.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useCallback, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuProps } from '@elastic/eui/src/components/context_menu/context_menu'; - -export const TableRowActionsNested = React.memo<{ panels: EuiContextMenuProps['panels'] }>( - ({ panels }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts index 73771fa3cb343..5ef7f45faec48 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts @@ -43,7 +43,6 @@ export const PAGE_ROUTING_PATHS = { configurations: '/configs', configurations_list: '/configs', configuration_details: '/configs/:configId/:tabId?', - configuration_details_yaml: '/configs/:configId/yaml', configuration_details_settings: '/configs/:configId/settings', add_datasource_from_configuration: '/configs/:configId/add-datasource', add_datasource_from_integration: '/integrations/:pkgkey/add-datasource', diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts index a752ad2a8912b..31a511f2d79dc 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -10,6 +10,7 @@ export { useConfig, ConfigContext } from './use_config'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; export { useBreadcrumbs } from './use_breadcrumbs'; export { useLink } from './use_link'; +export { useKibanaLink } from './use_kibana_link'; export { usePackageIconType, UsePackageIconType } from './use_package_icon_type'; export { usePagination, Pagination } from './use_pagination'; export { useDebounce } from './use_debounce'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts index f80c468677f48..45ca6047b0d96 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts @@ -14,6 +14,7 @@ import { agentConfigRouteService } from '../../services'; import { GetAgentConfigsResponse, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, @@ -39,7 +40,7 @@ export const useGetOneAgentConfig = (agentConfigId: string | undefined) => { }; export const useGetOneAgentConfigFull = (agentConfigId: string) => { - return useRequest({ + return useRequest({ path: agentConfigRouteService.getInfoFullPath(agentConfigId), method: 'get', }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx new file mode 100644 index 0000000000000..78ed228012691 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; +import { useCapabilities, useLink } from '../../../hooks'; +import { ContextMenuActions } from '../../../components'; +import { ConfigYamlFlyout } from './config_yaml_flyout'; + +export const AgentConfigActionMenu = memo<{ configId: string; fullButton?: boolean }>( + ({ configId, fullButton = false }) => { + const { getHref } = useLink(); + const hasWriteCapabilities = useCapabilities().write; + const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); + return ( + <> + {isYamlFlyoutOpen ? ( + + setIsYamlFlyoutOpen(false)} /> + + ) : null} + + ), + } + : undefined + } + items={[ + setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} + key="viewConfig" + > + + , + + + , + ]} + /> + + ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx new file mode 100644 index 0000000000000..6cf60fe1dc507 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_yaml_flyout.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; +import styled from 'styled-components'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import { useGetOneAgentConfigFull, useGetOneAgentConfig, useCore } from '../../../hooks'; +import { Loading } from '../../../components'; +import { configToYaml, agentConfigRouteService } from '../../../services'; + +const FlyoutBody = styled(EuiFlyoutBody)` + .euiFlyoutBody__overflowContent { + padding: 0; + } +`; + +export const ConfigYamlFlyout = memo<{ configId: string; onClose: () => void }>( + ({ configId, onClose }) => { + const core = useCore(); + const { isLoading: isLoadingYaml, data: yamlData } = useGetOneAgentConfigFull(configId); + const { data: configData } = useGetOneAgentConfig(configId); + + const body = + isLoadingYaml && !yamlData ? ( + + ) : ( + + {configToYaml(yamlData!.item)} + + ); + + const downloadLink = core.http.basePath.prepend( + agentConfigRouteService.getInfoFullDownloadPath(configId) + ); + + return ( + + + +

+ {configData?.item ? ( + + ) : ( + + )} +

+
+
+ {body} + + + + + + + + + + + + + + +
+ ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts index c1811b99588a8..f3ec15e0f477d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts @@ -3,8 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - export { AgentConfigForm, agentConfigFormValidation } from './config_form'; export { AgentConfigDeleteProvider } from './config_delete_provider'; +export { DatasourceDeleteProvider } from './datasource_delete_provider'; export { LinkedAgentCount } from './linked_agent_count'; export { ConfirmDeployConfigModal } from './confirm_deploy_modal'; +export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; +export { AgentConfigActionMenu } from './actions_menu'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx deleted file mode 100644 index 2f9a11ef76704..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/table_row_actions.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useCallback, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EuiContextMenuPanelProps } from '@elastic/eui/src/components/context_menu/context_menu_panel'; - -export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>( - ({ items }) => { - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - ); - } -); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx index 316b7eed491b9..01505fcf4c65e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx @@ -17,12 +17,10 @@ import { EuiFlexItem, } from '@elastic/eui'; import { AgentConfig, Datasource } from '../../../../../types'; -import { TableRowActions } from '../../../components/table_row_actions'; -import { DangerEuiContextMenuItem } from '../../../components/danger_eui_context_menu_item'; +import { PackageIcon, ContextMenuActions } from '../../../../../components'; +import { DatasourceDeleteProvider, DangerEuiContextMenuItem } from '../../../components'; import { useCapabilities, useLink } from '../../../../../hooks'; -import { DatasourceDeleteProvider } from '../../../components/datasource_delete_provider'; -import { useConfigRefresh } from '../../hooks/use_config'; -import { PackageIcon } from '../../../../../components/package_icon'; +import { useConfigRefresh } from '../../hooks'; interface InMemoryDatasource extends Datasource { streams: { total: number; enabled: number }; @@ -197,7 +195,7 @@ export const DatasourcesTable: React.FunctionComponent = ({ actions: [ { render: (datasource: InMemoryDatasource) => ( - (({ config }) => { - const fullConfigRequest = useGetOneAgentConfigFull(config.id); - - if (fullConfigRequest.isLoading && !fullConfigRequest.data) { - return ; - } - - return ( - - - - {dump(fullConfigRequest.data.item, { - sortKeys: (keyA: string, keyB: string) => { - const indexA = CONFIG_KEYS_ORDER.indexOf(keyA); - const indexB = CONFIG_KEYS_ORDER.indexOf(keyB); - if (indexA >= 0 && indexB < 0) { - return -1; - } - - if (indexA < 0 && indexB >= 0) { - return 1; - } - - return indexA - indexB; - }, - })} - - - - ); -}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index 3f886645b5339..1dd7e660deaa9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -27,10 +27,8 @@ import { useGetOneAgentConfig, useLink, useBreadcrumbs } from '../../../hooks'; import { Loading } from '../../../components'; import { WithHeaderLayout } from '../../../layouts'; import { ConfigRefreshContext, useGetAgentStatus, AgentStatusRefreshContext } from './hooks'; -import { LinkedAgentCount } from '../components'; -import { ConfigDatasourcesView } from './components/datasources'; -import { ConfigYamlView } from './components/yaml'; -import { ConfigSettingsView } from './components/settings'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; +import { ConfigDatasourcesView, ConfigSettingsView } from './components'; const Divider = styled.div` width: 0; @@ -147,21 +145,31 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { )) || '', }, + { isDivider: true }, + { + content: agentConfig && , + }, ].map((item, index) => ( {item.isDivider ?? false ? ( - ) : ( + ) : item.label ? ( - {item.label} - {item.content} + + {item.label} + + + {item.content} + + ) : ( + item.content )} ))} ), - [agentConfig, agentStatus] + [agentConfig, configId, agentStatus] ); const headerTabs = useMemo(() => { @@ -174,14 +182,6 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { href: getHref('configuration_details', { configId, tabId: 'datasources' }), isSelected: tabId === '' || tabId === 'datasources', }, - { - id: 'yaml', - name: i18n.translate('xpack.ingestManager.configDetails.subTabs.yamlTabText', { - defaultMessage: 'YAML', - }), - href: getHref('configuration_details', { configId, tabId: 'yaml' }), - isSelected: tabId === 'yaml', - }, { id: 'settings', name: i18n.translate('xpack.ingestManager.configDetails.subTabs.settingsTabText', { @@ -254,12 +254,6 @@ const AgentConfigDetailsContent: React.FunctionComponent<{ agentConfig: AgentCon useBreadcrumbs('configuration_details', { configName: agentConfig.name }); return ( - { - return ; - }} - /> { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx index 5b4066e53f0c8..0d43d8856c2fb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { CSSProperties, memo, useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiSpacer, EuiText, @@ -16,7 +16,6 @@ import { EuiTableActionsColumnType, EuiTableFieldDataColumnType, EuiTextColor, - EuiContextMenuItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; @@ -33,16 +32,9 @@ import { useUrlParams, useBreadcrumbs, } from '../../../hooks'; +import { SearchBar } from '../../../components'; +import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; import { CreateAgentConfigFlyout } from './components'; -import { SearchBar } from '../../../components/search_bar'; -import { LinkedAgentCount } from '../components'; -import { TableRowActions } from '../components/table_row_actions'; - -const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', -}); const AgentConfigListPageLayout: React.FunctionComponent = ({ children }) => ( ( ); -const ConfigRowActions = memo<{ config: AgentConfig; onDelete: () => void }>( - ({ config, onDelete }) => { - const { getHref } = useLink(); - const hasWriteCapabilities = useCapabilities().write; - - return ( - - - , - - - - , - // - // - // , - ]} - /> - ); - } -); - export const AgentConfigListPage: React.FunctionComponent<{}> = () => { useBreadcrumbs('configurations_list'); const { getHref, getPath } = useLink(); @@ -172,10 +122,10 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '20%', render: (name: string, agentConfig: AgentConfig) => ( - + {name || agentConfig.id} @@ -201,7 +151,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { width: '35%', truncateText: true, render: (description: AgentConfig['description']) => ( - + {description} ), @@ -239,9 +189,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { }), actions: [ { - render: (config: AgentConfig) => ( - sendRequest()} /> - ), + render: (config: AgentConfig) => , }, ], }, @@ -253,7 +201,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { } return cols; - }, [getHref, isFleetEnabled, sendRequest]); + }, [getHref, isFleetEnabled]); const createAgentConfigButton = useMemo( () => ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx index b87ae4c4561ff..cdc4f1c63a11d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx @@ -5,12 +5,11 @@ */ import React, { memo } from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { useKibanaLink } from '../../../../hooks/use_kibana_link'; +import { FormattedMessage } from '@kbn/i18n/react'; import { DataStream } from '../../../../types'; -import { TableRowActionsNested } from '../../../../components/table_row_actions_nested'; +import { useKibanaLink } from '../../../../hooks'; +import { ContextMenuActions } from '../../../../components'; export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastream }) => { const { dashboards } = datastream; @@ -78,5 +77,5 @@ export const DataStreamRowActions = memo<{ datastream: DataStream }>(({ datastre }); } - return ; + return ; }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 34a7ad8eb1efc..27e17f6b3df61 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -3,81 +3,70 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useState, useCallback } from 'react'; -import { EuiButton, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import React, { memo, useState } from 'react'; +import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { useCapabilities } from '../../../../hooks'; -import { useAgentRefresh } from '../hooks'; +import { ContextMenuActions } from '../../../../components'; import { AgentUnenrollProvider, AgentReassignConfigFlyout } from '../../components'; +import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; }> = memo(({ agent }) => { const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); - const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsActionsPopoverOpen(false), [ - setIsActionsPopoverOpen, - ]); - const handleToggleMenu = useCallback(() => setIsActionsPopoverOpen(!isActionsPopoverOpen), [ - isActionsPopoverOpen, - ]); const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); return ( <> {isReassignFlyoutOpen && ( - setIsReassignFlyoutOpen(false)} /> + + setIsReassignFlyoutOpen(false)} /> + )} - + - - } - isOpen={isActionsPopoverOpen} - closePopover={handleCloseMenu} - > - { - handleCloseMenu(); - setIsReassignFlyoutOpen(true); - }} - key="reassignConfig" - > - - , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, refreshAgent); - }} - > - - - )} - , - ]} - /> - + ), + }} + items={[ + { + setIsReassignFlyoutOpen(true); + }} + key="reassignConfig" + > + + , + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, refreshAgent); + }} + > + + + )} + , + ]} + /> ); }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index d5b8b393e7ed9..281a8d3a9745c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useCallback } from 'react'; +import React, { useState } from 'react'; import { EuiBasicTable, EuiButton, @@ -17,10 +17,9 @@ import { EuiPopover, EuiSpacer, EuiText, - EuiButtonIcon, - EuiContextMenuPanel, EuiContextMenuItem, EuiIcon, + EuiPortal, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; @@ -36,12 +35,10 @@ import { useLink, useBreadcrumbs, } from '../../../hooks'; -import { AgentReassignConfigFlyout } from '../components'; -import { SearchBar } from '../../../components/search_bar'; -import { AgentHealth } from '../components/agent_health'; -import { AgentUnenrollProvider } from '../components/agent_unenroll_provider'; +import { SearchBar, ContextMenuActions } from '../../../components'; import { AgentStatusKueryHelper } from '../../../services'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AgentReassignConfigFlyout, AgentHealth, AgentUnenrollProvider } from '../components'; const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ overflow: 'hidden', @@ -76,73 +73,53 @@ const RowActions = React.memo<{ agent: Agent; onReassignClick: () => void; refre ({ agent, refresh, onReassignClick }) => { const { getHref } = useLink(); const hasWriteCapabilites = useCapabilities().write; - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - , - { - handleCloseMenu(); - onReassignClick(); - }} - key="reassignConfig" - > - - , + + + , + { + onReassignClick(); + }} + key="reassignConfig" + > + + , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, () => { - refresh(); - }); - }} - > - - - )} - , - ]} - /> - + + {(unenrollAgentsPrompt) => ( + { + unenrollAgentsPrompt([agent.id], 1, () => { + refresh(); + }); + }} + > + + + )} + , + ]} + /> ); } ); @@ -387,13 +364,15 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> ) : null} {agentToReassign && ( - { - setAgentToReassignId(undefined); - agentsRequest.sendRequest(); - }} - /> + + { + setAgentToReassignId(undefined); + agentsRequest.sendRequest(); + }} + /> + )} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 085bad2d18375..6dbc8d67caaee 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -20,5 +20,6 @@ export { appRoutesService, packageToConfigDatasourceInputs, storedDatasourceToAgentDatasource, + configToYaml, AgentStatusKueryHelper, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 0a26a16d35cfd..05a97fd2e2a3c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -26,6 +26,7 @@ export { GetAgentConfigsResponse, GetAgentConfigsResponseItem, GetOneAgentConfigResponse, + GetFullAgentConfigResponse, CreateAgentConfigRequest, CreateAgentConfigResponse, UpdateAgentConfigRequest, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts index f74f898b2baf9..afc146cf90447 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; +import { RequestHandler, ResponseHeaders } from 'src/core/server'; import bluebird from 'bluebird'; +import { configToYaml } from '../../../common/services'; import { appContextService, agentConfigService, datasourceService } from '../../services'; import { listAgents } from '../../services/agents'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; @@ -229,3 +230,37 @@ export const getFullAgentConfig: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const { + params: { agentConfigId }, + } = request; + + try { + const fullAgentConfig = await agentConfigService.getFullConfig(soClient, agentConfigId); + if (fullAgentConfig) { + const body = configToYaml(fullAgentConfig); + const headers: ResponseHeaders = { + 'content-type': 'text/x-yaml', + 'content-disposition': `attachment; filename="elastic-agent-config-${fullAgentConfig.id}.yml"`, + }; + return response.ok({ + body, + headers, + }); + } else { + return response.customError({ + statusCode: 404, + body: { message: 'Agent config not found' }, + }); + } + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts index e630f3c959590..4f6cfb436b93b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts @@ -20,6 +20,7 @@ import { updateAgentConfigHandler, deleteAgentConfigsHandler, getFullAgentConfig, + downloadFullAgentConfig, } from './handlers'; export const registerRoutes = (router: IRouter) => { @@ -82,4 +83,14 @@ export const registerRoutes = (router: IRouter) => { }, getFullAgentConfig ); + + // Download one full agent config + router.get( + { + path: AGENT_CONFIG_API_ROUTES.FULL_INFO_DOWNLOAD_PATTERN, + validate: GetFullAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + downloadFullAgentConfig + ); }; diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index 483661b9de915..1a0fb262eeb7f 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentStatus } from '../../common/types/models'; +import { AgentStatus } from '../types'; import * as settingsService from './settings'; export { ESIndexPatternSavedObjectService } from './es_index_pattern'; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts index ab97ddc0ba723..123a413bb8442 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts @@ -39,4 +39,7 @@ export const GetFullAgentConfigRequestSchema = { params: schema.object({ agentConfigId: schema.string(), }), + query: schema.object({ + download: schema.maybe(schema.boolean()), + }), }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0557663e99696..18186f9a1bfcd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8121,11 +8121,9 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringText": "システムメトリックを収集", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "このオプションを有効にすると、システムメトリックと情報を収集するデータソースで構成をブートストラップできます。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentConfigList.actionsMenuText": "開く", "xpack.ingestManager.agentConfigList.addButton": "エージェント構成を作成", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "エージェント", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "フィルターを消去", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "データソースを作成", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "データソース", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "説明", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "エージェント構成を読み込み中...", @@ -8137,7 +8135,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "再読み込み", "xpack.ingestManager.agentConfigList.revisionNumber": "rev. {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最終更新日:", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "構成を表示", "xpack.ingestManager.agentDetails.actionsButton": "アクション", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "エージェント構成", "xpack.ingestManager.agentDetails.agentDetailsTitle": "エージェント'{id}'", @@ -8198,7 +8195,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "オンライン", "xpack.ingestManager.agentHealth.warningStatusText": "エラー", "xpack.ingestManager.agentList.actionsColumnTitle": "アクション", - "xpack.ingestManager.agentList.actionsMenuText": "開く", "xpack.ingestManager.agentList.addButton": "新しいエージェントを登録", "xpack.ingestManager.agentList.clearFiltersLinkText": "フィルターを消去", "xpack.ingestManager.agentList.configColumnTitle": "構成", @@ -8266,7 +8262,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "ストリーム", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "データソース", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "設定", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "データソース", "xpack.ingestManager.configDetails.summary.lastUpdated": "最終更新日:", "xpack.ingestManager.configDetails.summary.revision": "リビジョン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4f62da260d43f..195309f640a0a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8125,11 +8125,9 @@ "xpack.ingestManager.agentConfigForm.systemMonitoringText": "收集系统指标", "xpack.ingestManager.agentConfigForm.systemMonitoringTooltipText": "启用此选项可使用收集系统指标和信息的数据源启动您的配置。", "xpack.ingestManager.agentConfigList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentConfigList.actionsMenuText": "打开", "xpack.ingestManager.agentConfigList.addButton": "创建代理配置", "xpack.ingestManager.agentConfigList.agentsColumnTitle": "代理", "xpack.ingestManager.agentConfigList.clearFiltersLinkText": "清除筛选", - "xpack.ingestManager.agentConfigList.createDatasourceActionText": "创建数据源", "xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle": "数据源", "xpack.ingestManager.agentConfigList.descriptionColumnTitle": "描述", "xpack.ingestManager.agentConfigList.loadingAgentConfigsMessage": "正在加载代理配置……", @@ -8141,7 +8139,6 @@ "xpack.ingestManager.agentConfigList.reloadAgentConfigsButtonText": "重新加载", "xpack.ingestManager.agentConfigList.revisionNumber": "修订 {revNumber}", "xpack.ingestManager.agentConfigList.updatedOnColumnTitle": "最后更新时间", - "xpack.ingestManager.agentConfigList.viewConfigActionText": "查看配置", "xpack.ingestManager.agentDetails.actionsButton": "操作", "xpack.ingestManager.agentDetails.agentConfigurationLabel": "代理配置", "xpack.ingestManager.agentDetails.agentDetailsTitle": "代理“{id}”", @@ -8202,7 +8199,6 @@ "xpack.ingestManager.agentHealth.onlineStatusText": "联机", "xpack.ingestManager.agentHealth.warningStatusText": "错误", "xpack.ingestManager.agentList.actionsColumnTitle": "操作", - "xpack.ingestManager.agentList.actionsMenuText": "打开", "xpack.ingestManager.agentList.addButton": "注册新代理", "xpack.ingestManager.agentList.clearFiltersLinkText": "清除筛选", "xpack.ingestManager.agentList.configColumnTitle": "配置", @@ -8270,7 +8266,6 @@ "xpack.ingestManager.configDetails.datasourcesTable.streamsCountColumnTitle": "流计数", "xpack.ingestManager.configDetails.subTabs.datasourcesTabText": "数据源", "xpack.ingestManager.configDetails.subTabs.settingsTabText": "设置", - "xpack.ingestManager.configDetails.subTabs.yamlTabText": "YAML", "xpack.ingestManager.configDetails.summary.datasources": "数据源", "xpack.ingestManager.configDetails.summary.lastUpdated": "最后更新时间", "xpack.ingestManager.configDetails.summary.revision": "修订", From 1d86a2abd2ff4e5e726409c4f7bfb9a31f7ed0d3 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Tue, 2 Jun 2020 19:23:15 -0600 Subject: [PATCH 02/15] [SIEM][Exceptions] Add success/error toast component on alert state change (#67406) ## Summary Adds a success and error toast on open or close of alert(s). #65947 Screen Shot 2020-05-26 at 2 28 57 PM Screen Shot 2020-05-28 at 12 01 03 PM ### 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) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../alerts/components/signals/actions.tsx | 10 ++++-- .../signals/default_config.test.tsx | 15 ++++++++ .../components/signals/default_config.tsx | 6 ++++ .../alerts/components/signals/index.tsx | 35 +++++++++++++++++++ .../alerts/components/signals/translations.ts | 28 +++++++++++++++ .../public/alerts/components/signals/types.ts | 2 ++ .../detection_engine/signals/api.ts | 3 +- 7 files changed, 95 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx b/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx index c13e064bd1c3c..f01b39ccaba0d 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx @@ -56,17 +56,21 @@ export const updateSignalStatusAction = async ({ status, setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }: UpdateSignalStatusActionProps) => { try { setEventsLoading({ eventIds: signalIds, isLoading: true }); const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); - await updateSignalStatus({ query: queryObject, status }); + const response = await updateSignalStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: signalIds, isDeleted: true }); - } catch (e) { - // TODO: Show error toasts + + onAlertStatusUpdateSuccess(response.updated, status); + } catch (error) { + onAlertStatusUpdateFailure(status, error); } finally { setEventsLoading({ eventIds: signalIds, isLoading: false }); } diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx b/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx index 71da68108da7e..7821bfaaf9575 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx @@ -54,11 +54,16 @@ describe('signals default_config', () => { let createTimeline: CreateTimeline; let updateTimelineIsLoading: UpdateTimelineLoading; + let onAlertStatusUpdateSuccess: (count: number, status: string) => void; + let onAlertStatusUpdateFailure: (status: string, error: Error) => void; + beforeEach(() => { setEventsLoading = jest.fn(); setEventsDeleted = jest.fn(); createTimeline = jest.fn(); updateTimelineIsLoading = jest.fn(); + onAlertStatusUpdateSuccess = jest.fn(); + onAlertStatusUpdateFailure = jest.fn(); }); describe('timeline tooltip', () => { @@ -71,6 +76,8 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); const timelineAction = signalsActions[0].getAction({ eventId: 'even-id', @@ -97,6 +104,8 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); signalOpenAction = signalsActions[1].getAction({ @@ -119,6 +128,8 @@ describe('signals default_config', () => { status: 'open', setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); }); @@ -151,6 +162,8 @@ describe('signals default_config', () => { createTimeline, status: 'closed', updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); signalCloseAction = signalsActions[1].getAction({ @@ -173,6 +186,8 @@ describe('signals default_config', () => { status: 'closed', setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx b/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx index 05e0baba66d0a..1269f31064e9e 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx @@ -198,6 +198,8 @@ export const getSignalsActions = ({ createTimeline, status, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }: { apolloClient?: ApolloClient<{}>; canUserCRUD: boolean; @@ -207,6 +209,8 @@ export const getSignalsActions = ({ createTimeline: CreateTimeline; status: 'open' | 'closed'; updateTimelineIsLoading: UpdateTimelineLoading; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; }): TimelineAction[] => [ { getAction: ({ ecsData }: TimelineActionProps): JSX.Element => ( @@ -246,6 +250,8 @@ export const getSignalsActions = ({ status, setEventsLoading, setEventsDeleted, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }) } isDisabled={!canUserCRUD || !hasIndexWrite} diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx b/x-pack/plugins/siem/public/alerts/components/signals/index.tsx index eb19cfea97324..effb6a2450dc2 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/signals/index.tsx @@ -46,6 +46,11 @@ import { UpdateSignalsStatusProps, } from './types'; import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; +import { + useStateToaster, + displaySuccessToast, + displayErrorToast, +} from '../../../common/components/toasters'; export const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; @@ -91,6 +96,7 @@ export const SignalsTableComponent: React.FC = ({ signalsIndex !== '' ? [signalsIndex] : [] ); const kibana = useKibana(); + const [, dispatchToaster] = useStateToaster(); const getGlobalQuery = useCallback(() => { if (browserFields != null && indexPatterns != null) { @@ -146,6 +152,27 @@ export const SignalsTableComponent: React.FC = ({ [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] ); + const onAlertStatusUpdateSuccess = useCallback( + (count: number, status: string) => { + const title = + status === 'closed' + ? i18n.CLOSED_ALERT_SUCCESS_TOAST(count) + : i18n.OPENED_ALERT_SUCCESS_TOAST(count); + + displaySuccessToast(title, dispatchToaster); + }, + [dispatchToaster] + ); + + const onAlertStatusUpdateFailure = useCallback( + (status: string, error: Error) => { + const title = + status === 'closed' ? i18n.CLOSED_ALERT_FAILED_TOAST : i18n.OPENED_ALERT_FAILED_TOAST; + displayErrorToast(title, [error.message], dispatchToaster); + }, + [dispatchToaster] + ); + // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar useEffect(() => { if (!isSelectAllChecked) { @@ -189,6 +216,8 @@ export const SignalsTableComponent: React.FC = ({ status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }); refetchQuery(); }, @@ -198,6 +227,8 @@ export const SignalsTableComponent: React.FC = ({ setEventsDeletedCallback, setEventsLoadingCallback, showClearSelectionAction, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, ] ); @@ -244,6 +275,8 @@ export const SignalsTableComponent: React.FC = ({ setEventsDeleted: setEventsDeletedCallback, status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, }), [ apolloClient, @@ -254,6 +287,8 @@ export const SignalsTableComponent: React.FC = ({ setEventsLoadingCallback, setEventsDeletedCallback, updateTimelineIsLoading, + onAlertStatusUpdateSuccess, + onAlertStatusUpdateFailure, ] ); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/translations.ts index f68dcd932bc32..e49ff9846b9b7 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/signals/translations.ts @@ -101,3 +101,31 @@ export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( defaultMessage: 'Investigate in timeline', } ); + +export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.signals.closedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.signals.openedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.signals.closedAlertFailedToastMessage', + { + defaultMessage: 'Failed to close alert(s).', + } +); + +export const OPENED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.signals.openedAlertFailedToastMessage', + { + defaultMessage: 'Failed to open alert(s)', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/types.ts b/x-pack/plugins/siem/public/alerts/components/signals/types.ts index b3c770415ed57..542aa61074ce1 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/signals/types.ts @@ -37,6 +37,8 @@ export interface UpdateSignalStatusActionProps { status: 'open' | 'closed'; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + onAlertStatusUpdateSuccess: (count: number, status: string) => void; + onAlertStatusUpdateFailure: (status: string, error: Error) => void; } export type SendSignalsToTimeline = () => void; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts index 860305dd58e67..445f66457c59e 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ReindexResponse } from 'elasticsearch'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -54,7 +55,7 @@ export const updateSignalStatus = async ({ query, status, signal, -}: UpdateSignalStatusProps): Promise => +}: UpdateSignalStatusProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', body: JSON.stringify({ status, ...query }), From d8b66cf7d05b0ae6cc896498c98d70c8176bceb2 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 2 Jun 2020 21:23:37 -0400 Subject: [PATCH 03/15] [Uptime] Switch from `EuiFieldNumber` to `EuiFieldText` on settings page (#66425) * Switch from `EuiFieldNumber` to `EuiFieldText` on settings page. * Add unit tests for cert form fields. * Improve validation. * Add additional server-side settings check with tests. * Only create number object for validation when value is string. * Clean up validation function. Co-authored-by: Elastic Machine --- x-pack/plugins/uptime/common/translations.ts | 6 +- .../__tests__/certificate_form.test.tsx | 108 +++++++++++++++++- .../components/settings/certificate_form.tsx | 16 +-- .../public/pages/__tests__/settings.test.tsx | 35 ++++++ .../plugins/uptime/public/pages/settings.tsx | 18 ++- .../uptime/public/pages/translations.ts | 3 + .../__tests__/dynamic_settings.test.ts | 77 +++++++++++++ .../server/rest_api/dynamic_settings.ts | 17 ++- 8 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx create mode 100644 x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts diff --git a/x-pack/plugins/uptime/common/translations.ts b/x-pack/plugins/uptime/common/translations.ts index 678fe7cb1f984..81f46df86f02e 100644 --- a/x-pack/plugins/uptime/common/translations.ts +++ b/x-pack/plugins/uptime/common/translations.ts @@ -6,9 +6,13 @@ import { i18n } from '@kbn/i18n'; -export const VALUE_MUST_BE_GREATER_THEN_ZEO = i18n.translate( +export const VALUE_MUST_BE_GREATER_THAN_ZERO = i18n.translate( 'xpack.uptime.settings.invalid.error', { defaultMessage: 'Value must be greater than 0.', } ); + +export const VALUE_MUST_BE_AN_INTEGER = i18n.translate('xpack.uptime.settings.invalid.nanError', { + defaultMessage: 'Value must be an integer.', +}); diff --git a/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx b/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx index 8e2348a60ddab..2b587d23247e2 100644 --- a/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx +++ b/x-pack/plugins/uptime/public/components/settings/__tests__/certificate_form.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { CertificateExpirationForm } from '../certificate_form'; -import { shallowWithRouter } from '../../../lib'; +import { shallowWithRouter, mountWithRouter } from '../../../lib'; describe('CertificateForm', () => { it('shallow renders expected elements for valid props', () => { @@ -26,4 +26,110 @@ describe('CertificateForm', () => { ) ).toMatchSnapshot(); }); + + it('submits number values for certs settings fields', () => { + const onChangeMock = jest.fn(); + const wrapper = mountWithRouter( + + ); + + const inputs = wrapper.find('input'); + + expect(inputs).toHaveLength(2); + + // expiration threshold input + inputs.at(0).simulate('change', { + target: { + value: '23', + }, + }); + + // age threshold input + inputs.at(1).simulate('change', { + target: { + value: '56', + }, + }); + + expect(onChangeMock).toHaveBeenCalledTimes(2); + + expect(onChangeMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "certExpirationThreshold": 23, + }, + ] + `); + + expect(onChangeMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + Object { + "certAgeThreshold": 56, + }, + ] + `); + }); + + it('submits undefined for NaN values', () => { + const onChangeMock = jest.fn(); + const wrapper = mountWithRouter( + + ); + + const inputs = wrapper.find('input'); + + expect(inputs).toHaveLength(2); + + // expiration threshold input + inputs.at(0).simulate('change', { + target: { + value: 'A', + }, + }); + + // age threshold input + inputs.at(1).simulate('change', { + target: { + value: 'g', + }, + }); + + expect(onChangeMock).toHaveBeenCalledTimes(2); + + expect(onChangeMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "certExpirationThreshold": undefined, + }, + ] + `); + + expect(onChangeMock.mock.calls[1]).toMatchInlineSnapshot(` + Array [ + Object { + "certAgeThreshold": undefined, + }, + ] + `); + }); }); diff --git a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx index 38c0d0663c937..dcc3b97575983 100644 --- a/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx +++ b/x-pack/plugins/uptime/public/components/settings/certificate_form.tsx @@ -10,7 +10,7 @@ import { EuiDescribedFormGroup, EuiFormRow, EuiCode, - EuiFieldNumber, + EuiFieldText, EuiText, EuiTitle, EuiSpacer, @@ -80,17 +80,17 @@ export const CertificateExpirationForm: React.FC = ({ > - onChange({ - certExpirationThreshold: Number(e.target.value), + certExpirationThreshold: Number(e.target.value) || undefined, }) } /> @@ -128,17 +128,17 @@ export const CertificateExpirationForm: React.FC = ({ > - + onChange={({ target: { value } }) => onChange({ - certAgeThreshold: Number(value), + certAgeThreshold: Number(value) || undefined, }) } /> diff --git a/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx b/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx new file mode 100644 index 0000000000000..601feee8f8159 --- /dev/null +++ b/x-pack/plugins/uptime/public/pages/__tests__/settings.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isValidCertVal } from '../settings'; + +describe('settings', () => { + describe('isValidCertVal', () => { + it('handles NaN values', () => { + expect(isValidCertVal(NaN)).toMatchInlineSnapshot(`"Must be a number."`); + }); + + it('handles undefined', () => { + expect(isValidCertVal(undefined)).toMatchInlineSnapshot(`"Must be a number."`); + }); + + it('handles non-integer numbers', () => { + expect(isValidCertVal(23.5)).toMatchInlineSnapshot(`"Value must be an integer."`); + }); + + it('handles values less than 0', () => { + expect(isValidCertVal(-1)).toMatchInlineSnapshot(`"Value must be greater than 0."`); + }); + + it('handles 0', () => { + expect(isValidCertVal(0)).toMatchInlineSnapshot(`"Value must be greater than 0."`); + }); + + it('allows valid integer numbers', () => { + expect(isValidCertVal(67)).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/pages/settings.tsx b/x-pack/plugins/uptime/public/pages/settings.tsx index 807da83821261..6285c0cc0f1da 100644 --- a/x-pack/plugins/uptime/public/pages/settings.tsx +++ b/x-pack/plugins/uptime/public/pages/settings.tsx @@ -31,7 +31,10 @@ import { OnFieldChangeType, } from '../components/settings/certificate_form'; import * as Translations from './translations'; -import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; +import { + VALUE_MUST_BE_GREATER_THAN_ZERO, + VALUE_MUST_BE_AN_INTEGER, +} from '../../common/translations'; interface SettingsPageFieldErrors { heartbeatIndices: string | ''; @@ -47,12 +50,15 @@ export interface SettingsFormProps { isDisabled: boolean; } -const isValidCertVal = (val: string | number) => { - if (val === '') { - return Translations.BLANK_STR; +export const isValidCertVal = (val?: number): string | undefined => { + if (val === undefined || isNaN(val)) { + return Translations.settings.mustBeNumber; + } + if (val <= 0) { + return VALUE_MUST_BE_GREATER_THAN_ZERO; } - if (val === 0) { - return VALUE_MUST_BE_GREATER_THEN_ZEO; + if (val % 1) { + return VALUE_MUST_BE_AN_INTEGER; } }; diff --git a/x-pack/plugins/uptime/public/pages/translations.ts b/x-pack/plugins/uptime/public/pages/translations.ts index 8ed5503235884..dae4760edc469 100644 --- a/x-pack/plugins/uptime/public/pages/translations.ts +++ b/x-pack/plugins/uptime/public/pages/translations.ts @@ -35,6 +35,9 @@ export const settings = { returnToOverviewLinkLabel: i18n.translate('xpack.uptime.settings.returnToOverviewLinkLabel', { defaultMessage: 'Return to overview', }), + mustBeNumber: i18n.translate('xpack.uptime.settings.blankNumberField.error', { + defaultMessage: 'Must be a number.', + }), }; export const BLANK_STR = i18n.translate('xpack.uptime.settings.blank.error', { diff --git a/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts b/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts new file mode 100644 index 0000000000000..32e6385bc9fa2 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/__tests__/dynamic_settings.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { validateCertsValues } from '../dynamic_settings'; + +describe('dynamic settings', () => { + describe('validateCertValues', () => { + it(`doesn't allow age threshold values less than 0`, () => { + expect( + validateCertsValues({ + certAgeThreshold: -1, + certExpirationThreshold: 2, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certAgeThreshold": "Value must be greater than 0.", + } + `); + }); + + it(`doesn't allow non-integer age threshold values`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 10.2, + certExpirationThreshold: 2, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certAgeThreshold": "Value must be an integer.", + } + `); + }); + + it(`doesn't allow expiration threshold values less than 0`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: -1, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certExpirationThreshold": "Value must be greater than 0.", + } + `); + }); + + it(`doesn't allow non-integer expiration threshold values`, () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: 1.23, + heartbeatIndices: 'foo', + }) + ).toMatchInlineSnapshot(` + Object { + "certExpirationThreshold": "Value must be an integer.", + } + `); + }); + + it('allows valid values', () => { + expect( + validateCertsValues({ + certAgeThreshold: 2, + certExpirationThreshold: 13, + heartbeatIndices: 'foo', + }) + ).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts index c7d532d932aa6..6dba36ec0613d 100644 --- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts @@ -11,7 +11,10 @@ import { UMServerLibs } from '../lib/lib'; import { DynamicSettings, DynamicSettingsType } from '../../common/runtime_types'; import { UMRestApiRouteFactory } from '.'; import { savedObjectsAdapter } from '../lib/saved_objects'; -import { VALUE_MUST_BE_GREATER_THEN_ZEO } from '../../common/translations'; +import { + VALUE_MUST_BE_GREATER_THAN_ZERO, + VALUE_MUST_BE_AN_INTEGER, +} from '../../common/translations'; export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', @@ -24,13 +27,19 @@ export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSer }, }); -const validateCertsValues = (settings: DynamicSettings) => { +export const validateCertsValues = ( + settings: DynamicSettings +): Record | undefined => { const errors: any = {}; if (settings.certAgeThreshold <= 0) { - errors.certAgeThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + errors.certAgeThreshold = VALUE_MUST_BE_GREATER_THAN_ZERO; + } else if (settings.certAgeThreshold % 1) { + errors.certAgeThreshold = VALUE_MUST_BE_AN_INTEGER; } if (settings.certExpirationThreshold <= 0) { - errors.certExpirationThreshold = VALUE_MUST_BE_GREATER_THEN_ZEO; + errors.certExpirationThreshold = VALUE_MUST_BE_GREATER_THAN_ZERO; + } else if (settings.certExpirationThreshold % 1) { + errors.certExpirationThreshold = VALUE_MUST_BE_AN_INTEGER; } if (errors.certAgeThreshold || errors.certExpirationThreshold) { return errors; From fdd0c47e75f80b34e3c2e50b6ce6186cb8bc0abe Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 2 Jun 2020 19:42:03 -0700 Subject: [PATCH 04/15] skip flaky suite (#53749) --- x-pack/test/functional/apps/graph/graph.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index d9e8b22c45d50..803e5e8f80d70 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('graph', function () { + // FLAKY: https://github.com/elastic/kibana/issues/53749 + describe.skip('graph', function () { before(async () => { await browser.setWindowSize(1600, 1000); log.debug('load graph/secrepo data'); From 4c30d912a9c08079c8caf96f73a16f531c125a5f Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 2 Jun 2020 21:50:16 -0600 Subject: [PATCH 05/15] [SIEM] [Detections] Renames Signals to Alerts (#67731) ## Summary Resolves https://github.com/elastic/kibana/issues/65944 Renames `Signals` -> `Alerts` on the main Detection Engine page. Including: * Timeline Event Selector * Alerts Histogram * Alerts Table Does not include: * `Detections` -> `Alerts` navigation rename * `SignalsByCategory` rename as there already exists an `AlertsByCategory`, verify changing to `ExternalAlertsByCategory` * Anything server-side or related to `siemSignalsIndex` ### Checklist - [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) - [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- x-pack/plugins/siem/cypress/README.md | 8 +- .../cypress/integration/detections.spec.ts | 231 +++++++++--------- .../integration/detections_timeline.spec.ts | 22 +- .../signal_detection_rules.spec.ts | 18 +- .../signal_detection_rules_custom.spec.ts | 24 +- .../signal_detection_rules_export.spec.ts | 14 +- .../signal_detection_rules_ml.spec.ts | 18 +- .../signal_detection_rules_prebuilt.spec.ts | 24 +- ...tion_rules.ts => alert_detection_rules.ts} | 0 .../siem/cypress/screens/detections.ts | 28 +-- ...tion_rules.ts => alert_detection_rules.ts} | 2 +- .../plugins/siem/cypress/tasks/detections.ts | 78 +++--- .../alerts_histogram.test.tsx} | 6 +- .../alerts_histogram.tsx} | 14 +- .../config.ts | 4 +- .../helpers.test.tsx | 8 +- .../helpers.tsx | 34 ++- .../index.test.tsx | 6 +- .../index.tsx | 106 ++++---- .../translations.ts | 48 ++-- .../types.ts | 18 +- .../alerts/components/alerts_info/index.tsx | 49 ++++ .../query.dsl.ts | 2 +- .../{signals_info => alerts_info}/types.ts | 0 .../actions.test.tsx | 44 ++-- .../{signals => alerts_table}/actions.tsx | 38 +-- .../alerts_filter_group}/index.test.tsx | 6 +- .../alerts_filter_group}/index.tsx | 16 +- .../alerts_utility_bar}/index.test.tsx | 10 +- .../alerts_utility_bar}/index.tsx | 36 +-- .../alerts_utility_bar/translations.ts | 77 ++++++ .../default_config.test.tsx | 80 +++--- .../default_config.tsx | 50 ++-- .../{signals => alerts_table}/helpers.test.ts | 0 .../{signals => alerts_table}/helpers.ts | 2 +- .../{signals => alerts_table}/index.test.tsx | 8 +- .../{signals => alerts_table}/index.tsx | 94 +++---- .../components/alerts_table/translations.ts | 128 ++++++++++ .../{signals => alerts_table}/types.ts | 18 +- .../translations.ts | 2 +- .../index.test.tsx | 6 +- .../index.tsx | 8 +- .../translations.ts | 14 +- .../rules/pre_packaged_rules/translations.ts | 2 +- .../rules/rule_actions_field/index.tsx | 2 +- .../rules/step_about_rule/schema.tsx | 2 +- .../rules/step_define_rule/schema.tsx | 2 +- .../rules/step_schedule_rule/schema.tsx | 5 +- .../signals_utility_bar/translations.ts | 77 ------ .../alerts/components/signals/translations.ts | 131 ---------- .../alerts/components/signals_info/index.tsx | 49 ---- .../components/user_info/index.test.tsx | 8 +- .../alerts/components/user_info/index.tsx | 4 +- .../{signals => alerts}/__mocks__/api.ts | 20 +- .../{signals => alerts}/api.test.ts | 58 ++--- .../{signals => alerts}/api.ts | 34 +-- .../{signals => alerts}/mock.ts | 18 +- .../{signals => alerts}/translations.ts | 14 +- .../{signals => alerts}/types.ts | 12 +- .../use_privilege_user.test.tsx | 0 .../use_privilege_user.tsx | 0 .../{signals => alerts}/use_query.test.tsx | 62 ++--- .../{signals => alerts}/use_query.tsx | 30 +-- .../use_signal_index.test.tsx | 4 +- .../{signals => alerts}/use_signal_index.tsx | 0 .../detection_engine/detection_engine.tsx | 116 +++------ .../alerts/pages/detection_engine/index.tsx | 9 +- .../rules/create/helpers.test.ts | 2 +- .../rules/create/translations.ts | 2 +- .../detection_engine/rules/details/index.tsx | 52 ++-- .../rules/details/translations.ts | 2 +- .../pages/detection_engine/rules/index.tsx | 2 +- .../detection_engine/rules/translations.ts | 11 +- .../detection_engine/rules/utils.test.ts | 2 +- .../pages/detection_engine/rules/utils.ts | 7 - .../pages/detection_engine/translations.ts | 16 +- .../alerts/pages/detection_engine/types.ts | 10 - .../components/drag_and_drop/helpers.ts | 2 +- .../common/components/link_to/link_to.tsx | 6 - .../link_to/redirect_to_detection_engine.tsx | 11 +- .../popover_description.test.tsx.snap | 2 +- .../ml_popover/popover_description.tsx | 2 +- .../common/components/top_n/helpers.test.tsx | 6 +- .../public/common/components/top_n/helpers.ts | 16 +- .../common/components/top_n/index.test.tsx | 6 +- .../public/common/components/top_n/index.tsx | 10 +- .../common/components/top_n/top_n.test.tsx | 10 +- .../common/components/top_n/translations.ts | 4 +- .../siem/public/common/mock/mock_ecs.ts | 2 +- .../public/common/mock/timeline_results.ts | 4 +- .../components/alerts_by_category/index.tsx | 11 +- .../components/signals_by_category/index.tsx | 18 +- .../public/overview/pages/translations.ts | 4 +- .../components/fields_browser/header.tsx | 10 +- .../timelines/components/timeline/index.tsx | 2 +- .../timeline/search_or_filter/pick_events.tsx | 2 +- .../public/timelines/containers/index.tsx | 4 +- .../public/timelines/store/timeline/model.ts | 2 +- .../routes/__mocks__/request_responses.ts | 2 +- .../lib/matrix_histogram/translations.ts | 2 +- .../translations/translations/ja-JP.json | 51 ---- .../translations/translations/zh-CN.json | 51 ---- .../{signals => alerts}/data.json.gz | Bin .../{signals => alerts}/mappings.json | 0 .../data.json.gz | Bin .../mappings.json | 0 .../data.json.gz | Bin .../mappings.json | 0 108 files changed, 1099 insertions(+), 1305 deletions(-) rename x-pack/plugins/siem/cypress/screens/{signal_detection_rules.ts => alert_detection_rules.ts} (100%) rename x-pack/plugins/siem/cypress/tasks/{signal_detection_rules.ts => alert_detection_rules.ts} (98%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel/signals_histogram.test.tsx => alerts_histogram_panel/alerts_histogram.test.tsx} (84%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel/signals_histogram.tsx => alerts_histogram_panel/alerts_histogram.tsx} (89%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/config.ts (88%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/helpers.test.tsx (89%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/helpers.tsx (67%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/index.test.tsx (85%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/index.tsx (72%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/translations.ts (50%) rename x-pack/plugins/siem/public/alerts/components/{signals_histogram_panel => alerts_histogram_panel}/types.ts (70%) create mode 100644 x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx rename x-pack/plugins/siem/public/alerts/components/{signals_info => alerts_info}/query.dsl.ts (91%) rename x-pack/plugins/siem/public/alerts/components/{signals_info => alerts_info}/types.ts (100%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/actions.test.tsx (92%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/actions.tsx (82%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_filter_group => alerts_table/alerts_filter_group}/index.test.tsx (68%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_filter_group => alerts_table/alerts_filter_group}/index.tsx (74%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_utility_bar => alerts_table/alerts_utility_bar}/index.test.tsx (76%) rename x-pack/plugins/siem/public/alerts/components/{signals/signals_utility_bar => alerts_table/alerts_utility_bar}/index.tsx (77%) create mode 100644 x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/default_config.test.tsx (67%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/default_config.tsx (81%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/helpers.test.ts (100%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/helpers.ts (98%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/index.test.tsx (85%) rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/index.tsx (81%) create mode 100644 x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts rename x-pack/plugins/siem/public/alerts/components/{signals => alerts_table}/types.ts (77%) rename x-pack/plugins/siem/public/alerts/components/{no_write_signals_callout => no_write_alerts_callout}/index.test.tsx (72%) rename x-pack/plugins/siem/public/alerts/components/{no_write_signals_callout => no_write_alerts_callout}/index.tsx (72%) rename x-pack/plugins/siem/public/alerts/components/{no_write_signals_callout => no_write_alerts_callout}/translations.ts (52%) delete mode 100644 x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts delete mode 100644 x-pack/plugins/siem/public/alerts/components/signals/translations.ts delete mode 100644 x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/__mocks__/api.ts (56%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/api.test.ts (68%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/api.ts (72%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/mock.ts (98%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/translations.ts (55%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/types.ts (88%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_privilege_user.test.tsx (100%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_privilege_user.tsx (100%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_query.test.tsx (61%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_query.tsx (69%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_signal_index.test.tsx (96%) rename x-pack/plugins/siem/public/alerts/containers/detection_engine/{signals => alerts}/use_signal_index.tsx (100%) delete mode 100644 x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts rename x-pack/test/siem_cypress/es_archives/{signals => alerts}/data.json.gz (100%) rename x-pack/test/siem_cypress/es_archives/{signals => alerts}/mappings.json (100%) rename x-pack/test/siem_cypress/es_archives/{closed_signals => closed_alerts}/data.json.gz (100%) rename x-pack/test/siem_cypress/es_archives/{closed_signals => closed_alerts}/mappings.json (100%) rename x-pack/test/siem_cypress/es_archives/{timeline_signals => timeline_alerts}/data.json.gz (100%) rename x-pack/test/siem_cypress/es_archives/{timeline_signals => timeline_alerts}/mappings.json (100%) diff --git a/x-pack/plugins/siem/cypress/README.md b/x-pack/plugins/siem/cypress/README.md index d84c66fec1c3a..b50924532726c 100644 --- a/x-pack/plugins/siem/cypress/README.md +++ b/x-pack/plugins/siem/cypress/README.md @@ -176,16 +176,16 @@ The current archives can be found in `x-pack/test/siem_cypress/es_archives/`. - siem-kibana - siem-es - jessie -- closed_signals - - Set of data with 108 closed signals linked to "Signals test" custom rule. +- closed_alerts + - Set of data with 108 closed alerts linked to "Alerts test" custom rule. - custome_rules - Set if data with just 4 custom activated rules. - empty_kibana - Empty kibana board. - prebuilt_rules_loaded - Elastic prebuilt loaded rules and deactivated. -- signals - - Set of data with 108 opened signals linked to "Signals test" custom rule. +- alerts + - Set of data with 108 opened alerts linked to "Alerts test" custom rule. ### How to generate a new archive diff --git a/x-pack/plugins/siem/cypress/integration/detections.spec.ts b/x-pack/plugins/siem/cypress/integration/detections.spec.ts index 91727595708f6..23e84070e93ae 100644 --- a/x-pack/plugins/siem/cypress/integration/detections.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/detections.spec.ts @@ -4,24 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ import { - NUMBER_OF_SIGNALS, - OPEN_CLOSE_SIGNALS_BTN, - SELECTED_SIGNALS, - SHOWING_SIGNALS, - SIGNALS, + NUMBER_OF_ALERTS, + OPEN_CLOSE_ALERTS_BTN, + SELECTED_ALERTS, + SHOWING_ALERTS, + ALERTS, } from '../screens/detections'; import { - closeFirstSignal, - closeSignals, - goToClosedSignals, - goToOpenedSignals, - openFirstSignal, - openSignals, - selectNumberOfSignals, - waitForSignalsPanelToBeLoaded, - waitForSignals, - waitForSignalsToBeLoaded, + closeFirstAlert, + closeAlerts, + goToClosedAlerts, + goToOpenedAlerts, + openFirstAlert, + openAlerts, + selectNumberOfAlerts, + waitForAlertsPanelToBeLoaded, + waitForAlerts, + waitForAlertsToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -29,179 +29,176 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; describe('Detections', () => { - context('Closing signals', () => { + context('Closing alerts', () => { beforeEach(() => { - esArchiverLoad('signals'); + esArchiverLoad('alerts'); loginAndWaitForPage(DETECTIONS); }); - it('Closes and opens signals', () => { - waitForSignalsPanelToBeLoaded(); - waitForSignalsToBeLoaded(); + it('Closes and opens alerts', () => { + waitForAlertsPanelToBeLoaded(); + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - cy.get(SHOWING_SIGNALS).should('have.text', `Showing ${numberOfSignals} signals`); + .then((numberOfAlerts) => { + cy.get(SHOWING_ALERTS).should('have.text', `Showing ${numberOfAlerts} alerts`); - const numberOfSignalsToBeClosed = 3; - selectNumberOfSignals(numberOfSignalsToBeClosed); + const numberOfAlertsToBeClosed = 3; + selectNumberOfAlerts(numberOfAlertsToBeClosed); - cy.get(SELECTED_SIGNALS).should( + cy.get(SELECTED_ALERTS).should( 'have.text', - `Selected ${numberOfSignalsToBeClosed} signals` + `Selected ${numberOfAlertsToBeClosed} alerts` ); - closeSignals(); - waitForSignals(); + closeAlerts(); + waitForAlerts(); cy.reload(); - waitForSignals(); + waitForAlerts(); - const expectedNumberOfSignalsAfterClosing = +numberOfSignals - numberOfSignalsToBeClosed; - cy.get(NUMBER_OF_SIGNALS).should( + const expectedNumberOfAlertsAfterClosing = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).should( 'have.text', - expectedNumberOfSignalsAfterClosing.toString() + expectedNumberOfAlertsAfterClosing.toString() ); - cy.get(SHOWING_SIGNALS).should( + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfSignalsAfterClosing.toString()} signals` + `Showing ${expectedNumberOfAlertsAfterClosing.toString()} alerts` ); - goToClosedSignals(); - waitForSignals(); + goToClosedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS).should('have.text', numberOfSignalsToBeClosed.toString()); - cy.get(SHOWING_SIGNALS).should( + cy.get(NUMBER_OF_ALERTS).should('have.text', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${numberOfSignalsToBeClosed.toString()} signals` + `Showing ${numberOfAlertsToBeClosed.toString()} alerts` ); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); - const numberOfSignalsToBeOpened = 1; - selectNumberOfSignals(numberOfSignalsToBeOpened); + const numberOfAlertsToBeOpened = 1; + selectNumberOfAlerts(numberOfAlertsToBeOpened); - cy.get(SELECTED_SIGNALS).should( - 'have.text', - `Selected ${numberOfSignalsToBeOpened} signal` - ); + cy.get(SELECTED_ALERTS).should('have.text', `Selected ${numberOfAlertsToBeOpened} alert`); - openSignals(); - waitForSignals(); + openAlerts(); + waitForAlerts(); cy.reload(); - waitForSignalsToBeLoaded(); - waitForSignals(); - goToClosedSignals(); - waitForSignals(); + waitForAlertsToBeLoaded(); + waitForAlerts(); + goToClosedAlerts(); + waitForAlerts(); - const expectedNumberOfClosedSignalsAfterOpened = 2; - cy.get(NUMBER_OF_SIGNALS).should( + const expectedNumberOfClosedAlertsAfterOpened = 2; + cy.get(NUMBER_OF_ALERTS).should( 'have.text', - expectedNumberOfClosedSignalsAfterOpened.toString() + expectedNumberOfClosedAlertsAfterOpened.toString() ); - cy.get(SHOWING_SIGNALS).should( + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfClosedSignalsAfterOpened.toString()} signals` + `Showing ${expectedNumberOfClosedAlertsAfterOpened.toString()} alerts` ); - cy.get(SIGNALS).should('have.length', expectedNumberOfClosedSignalsAfterOpened); + cy.get(ALERTS).should('have.length', expectedNumberOfClosedAlertsAfterOpened); - goToOpenedSignals(); - waitForSignals(); + goToOpenedAlerts(); + waitForAlerts(); - const expectedNumberOfOpenedSignals = - +numberOfSignals - expectedNumberOfClosedSignalsAfterOpened; - cy.get(SHOWING_SIGNALS).should( + const expectedNumberOfOpenedAlerts = + +numberOfAlerts - expectedNumberOfClosedAlertsAfterOpened; + cy.get(SHOWING_ALERTS).should( 'have.text', - `Showing ${expectedNumberOfOpenedSignals.toString()} signals` + `Showing ${expectedNumberOfOpenedAlerts.toString()} alerts` ); cy.get('[data-test-subj="server-side-event-count"]').should( 'have.text', - expectedNumberOfOpenedSignals.toString() + expectedNumberOfOpenedAlerts.toString() ); }); }); - it('Closes one signal when more than one opened signals are selected', () => { - waitForSignalsToBeLoaded(); + it('Closes one alert when more than one opened alerts are selected', () => { + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - const numberOfSignalsToBeClosed = 1; - const numberOfSignalsToBeSelected = 3; + .then((numberOfAlerts) => { + const numberOfAlertsToBeClosed = 1; + const numberOfAlertsToBeSelected = 3; - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled'); - selectNumberOfSignals(numberOfSignalsToBeSelected); - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled'); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); - closeFirstSignal(); + closeFirstAlert(); cy.reload(); - waitForSignalsToBeLoaded(); - waitForSignals(); + waitForAlertsToBeLoaded(); + waitForAlerts(); - const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeClosed; - cy.get(NUMBER_OF_SIGNALS).invoke('text').should('eq', expectedNumberOfSignals.toString()); - cy.get(SHOWING_SIGNALS) + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeClosed; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`); + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); - goToClosedSignals(); - waitForSignals(); + goToClosedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .should('eql', numberOfSignalsToBeClosed.toString()); - cy.get(SHOWING_SIGNALS) + .should('eql', numberOfAlertsToBeClosed.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signal`); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + .should('eql', `Showing ${numberOfAlertsToBeClosed.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeClosed); }); }); }); - context('Opening signals', () => { + context('Opening alerts', () => { beforeEach(() => { - esArchiverLoad('closed_signals'); + esArchiverLoad('closed_alerts'); loginAndWaitForPage(DETECTIONS); }); - it('Open one signal when more than one closed signals are selected', () => { - waitForSignals(); - goToClosedSignals(); - waitForSignalsToBeLoaded(); + it('Open one alert when more than one closed alerts are selected', () => { + waitForAlerts(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .then((numberOfSignals) => { - const numberOfSignalsToBeOpened = 1; - const numberOfSignalsToBeSelected = 3; + .then((numberOfAlerts) => { + const numberOfAlertsToBeOpened = 1; + const numberOfAlertsToBeSelected = 3; - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled'); - selectNumberOfSignals(numberOfSignalsToBeSelected); - cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled'); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('have.attr', 'disabled'); + selectNumberOfAlerts(numberOfAlertsToBeSelected); + cy.get(OPEN_CLOSE_ALERTS_BTN).should('not.have.attr', 'disabled'); - openFirstSignal(); + openFirstAlert(); cy.reload(); - goToClosedSignals(); - waitForSignalsToBeLoaded(); - waitForSignals(); + goToClosedAlerts(); + waitForAlertsToBeLoaded(); + waitForAlerts(); - const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeOpened; - cy.get(NUMBER_OF_SIGNALS).invoke('text').should('eq', expectedNumberOfSignals.toString()); - cy.get(SHOWING_SIGNALS) + const expectedNumberOfAlerts = +numberOfAlerts - numberOfAlertsToBeOpened; + cy.get(NUMBER_OF_ALERTS).invoke('text').should('eq', expectedNumberOfAlerts.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`); + .should('eql', `Showing ${expectedNumberOfAlerts.toString()} alerts`); - goToOpenedSignals(); - waitForSignals(); + goToOpenedAlerts(); + waitForAlerts(); - cy.get(NUMBER_OF_SIGNALS) + cy.get(NUMBER_OF_ALERTS) .invoke('text') - .should('eql', numberOfSignalsToBeOpened.toString()); - cy.get(SHOWING_SIGNALS) + .should('eql', numberOfAlertsToBeOpened.toString()); + cy.get(SHOWING_ALERTS) .invoke('text') - .should('eql', `Showing ${numberOfSignalsToBeOpened.toString()} signal`); - cy.get(SIGNALS).should('have.length', numberOfSignalsToBeOpened); + .should('eql', `Showing ${numberOfAlertsToBeOpened.toString()} alert`); + cy.get(ALERTS).should('have.length', numberOfAlertsToBeOpened); }); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts b/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts index 6ea34f5203adc..d3ddb2ad71e30 100644 --- a/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/detections_timeline.spec.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SIGNAL_ID } from '../screens/detections'; +import { ALERT_ID } from '../screens/detections'; import { PROVIDER_BADGE } from '../screens/timeline'; import { - expandFirstSignal, - investigateFirstSignalInTimeline, - waitForSignalsPanelToBeLoaded, + expandFirstAlert, + investigateFirstAlertInTimeline, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -19,22 +19,22 @@ import { DETECTIONS } from '../urls/navigation'; describe('Detections timeline', () => { beforeEach(() => { - esArchiverLoad('timeline_signals'); + esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS); }); afterEach(() => { - esArchiverUnload('timeline_signals'); + esArchiverUnload('timeline_alerts'); }); - it('Investigate signal in default timeline', () => { - waitForSignalsPanelToBeLoaded(); - expandFirstSignal(); - cy.get(SIGNAL_ID) + it('Investigate alert in default timeline', () => { + waitForAlertsPanelToBeLoaded(); + expandFirstAlert(); + cy.get(ALERT_ID) .first() .invoke('text') .then((eventId) => { - investigateFirstSignalInTimeline(); + investigateFirstAlertInTimeline(); cy.get(PROVIDER_BADGE).invoke('text').should('eql', `_id: "${eventId}"`); }); }); diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts index d07850e23f05e..e8f9411c149d4 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules.spec.ts @@ -10,12 +10,12 @@ import { RULE_SWITCH, SECOND_RULE, SEVENTH_RULE, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { - goToManageSignalDetectionRules, - waitForSignalsPanelToBeLoaded, - waitForSignalsIndexToBeCreated, + goToManageAlertDetectionRules, + waitForAlertsPanelToBeLoaded, + waitForAlertsIndexToBeCreated, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -24,11 +24,11 @@ import { sortByActivatedRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRuleToBeActivated, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules', () => { +describe('Detection rules', () => { before(() => { esArchiverLoad('prebuilt_rules_loaded'); }); @@ -39,9 +39,9 @@ describe('Signal detection rules', () => { it('Sorts by activated rules', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); cy.get(RULE_NAME) .eq(FIFTH_RULE) diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts index 04762bbf352d2..e5cec16c48a37 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts @@ -36,7 +36,7 @@ import { RULES_TABLE, SEVERITY, SHOWING_RULES_TEXT, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { createAndActivateRule, @@ -44,9 +44,9 @@ import { fillDefineCustomRuleWithImportedQueryAndContinue, } from '../tasks/create_new_rule'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { changeToThreeHundredRowsPerPage, @@ -58,13 +58,13 @@ import { selectNumberOfRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules, custom', () => { +describe('Detection rules, custom', () => { before(() => { esArchiverLoad('custom_rule_with_timeline'); }); @@ -75,9 +75,9 @@ describe('Signal detection rules, custom', () => { it('Creates and activates a new custom rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); fillDefineCustomRuleWithImportedQueryAndContinue(newRule); @@ -170,9 +170,9 @@ describe('Deletes custom rules', () => { beforeEach(() => { esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); }); after(() => { diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts index aa1a111102160..4a12990438999 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_export.spec.ts @@ -5,13 +5,13 @@ */ import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { exportFirstRule } from '../tasks/signal_detection_rules'; +import { exportFirstRule } from '../tasks/alert_detection_rules'; import { DETECTIONS } from '../urls/navigation'; @@ -33,9 +33,9 @@ describe('Export rules', () => { it('Exports a custom rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); exportFirstRule(); cy.wait('@export').then((xhr) => { cy.readFile(EXPECTED_EXPORTED_RULE_FILE_PATH).then(($expectedExportedJson) => { diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts index cb04d8117a923..fd2dff27ad359 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts @@ -34,7 +34,7 @@ import { RULES_ROW, RULES_TABLE, SEVERITY, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { createAndActivateRule, @@ -43,9 +43,9 @@ import { selectMachineLearningRuleType, } from '../tasks/create_new_rule'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { changeToThreeHundredRowsPerPage, @@ -54,13 +54,13 @@ import { goToRuleDetails, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS } from '../urls/navigation'; -describe('Signal detection rules, machine learning', () => { +describe('Detection rules, machine learning', () => { before(() => { esArchiverLoad('prebuilt_rules_loaded'); }); @@ -71,9 +71,9 @@ describe('Signal detection rules, machine learning', () => { it('Creates and activates a new ml rule', () => { loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); goToCreateNewRule(); selectMachineLearningRuleType(); diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts index 005e24dad2a16..2cd087b2ca5e1 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_prebuilt.spec.ts @@ -10,7 +10,7 @@ import { RELOAD_PREBUILT_RULES_BTN, RULES_ROW, RULES_TABLE, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; import { changeToThreeHundredRowsPerPage, @@ -22,11 +22,11 @@ import { waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForPrebuiltDetectionRulesToBeLoaded, waitForRulesToBeLoaded, -} from '../tasks/signal_detection_rules'; +} from '../tasks/alert_detection_rules'; import { - goToManageSignalDetectionRules, - waitForSignalsIndexToBeCreated, - waitForSignalsPanelToBeLoaded, + goToManageAlertDetectionRules, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, } from '../tasks/detections'; import { esArchiverLoadEmptyKibana, esArchiverUnloadEmptyKibana } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; @@ -35,7 +35,7 @@ import { DETECTIONS } from '../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../objects/rule'; -describe('Signal detection rules, prebuilt rules', () => { +describe('Detection rules, prebuilt rules', () => { before(() => { esArchiverLoadEmptyKibana(); }); @@ -49,9 +49,9 @@ describe('Signal detection rules, prebuilt rules', () => { const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); @@ -74,9 +74,9 @@ describe('Deleting prebuilt rules', () => { esArchiverLoadEmptyKibana(); loginAndWaitForPageWithoutDateRange(DETECTIONS); - waitForSignalsPanelToBeLoaded(); - waitForSignalsIndexToBeCreated(); - goToManageSignalDetectionRules(); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertDetectionRules(); waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); loadPrebuiltDetectionRules(); waitForPrebuiltDetectionRulesToBeLoaded(); diff --git a/x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts b/x-pack/plugins/siem/cypress/screens/alert_detection_rules.ts similarity index 100% rename from x-pack/plugins/siem/cypress/screens/signal_detection_rules.ts rename to x-pack/plugins/siem/cypress/screens/alert_detection_rules.ts diff --git a/x-pack/plugins/siem/cypress/screens/detections.ts b/x-pack/plugins/siem/cypress/screens/detections.ts index d9ffa5b5a4ab2..b915bcba2f880 100644 --- a/x-pack/plugins/siem/cypress/screens/detections.ts +++ b/x-pack/plugins/siem/cypress/screens/detections.ts @@ -4,30 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CLOSED_SIGNALS_BTN = '[data-test-subj="closedSignals"]'; +export const CLOSED_ALERTS_BTN = '[data-test-subj="closedAlerts"]'; -export const EXPAND_SIGNAL_BTN = '[data-test-subj="expand-event"]'; +export const EXPAND_ALERT_BTN = '[data-test-subj="expand-event"]'; -export const LOADING_SIGNALS_PANEL = '[data-test-subj="loading-signals-panel"]'; +export const LOADING_ALERTS_PANEL = '[data-test-subj="loading-alerts-panel"]'; -export const MANAGE_SIGNAL_DETECTION_RULES_BTN = '[data-test-subj="manage-signal-detection-rules"]'; +export const MANAGE_ALERT_DETECTION_RULES_BTN = '[data-test-subj="manage-alert-detection-rules"]'; -export const NUMBER_OF_SIGNALS = '[data-test-subj="server-side-event-count"] .euiBadge__text'; +export const NUMBER_OF_ALERTS = '[data-test-subj="server-side-event-count"] .euiBadge__text'; -export const OPEN_CLOSE_SIGNAL_BTN = '[data-test-subj="update-signal-status-button"]'; +export const OPEN_CLOSE_ALERT_BTN = '[data-test-subj="update-alert-status-button"]'; -export const OPEN_CLOSE_SIGNALS_BTN = '[data-test-subj="openCloseSignal"] button'; +export const OPEN_CLOSE_ALERTS_BTN = '[data-test-subj="openCloseAlert"] button'; -export const OPENED_SIGNALS_BTN = '[data-test-subj="openSignals"]'; +export const OPENED_ALERTS_BTN = '[data-test-subj="openAlerts"]'; -export const SELECTED_SIGNALS = '[data-test-subj="selectedSignals"]'; +export const SELECTED_ALERTS = '[data-test-subj="selectedAlerts"]'; -export const SEND_SIGNAL_TO_TIMELINE_BTN = '[data-test-subj="send-signal-to-timeline-button"]'; +export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]'; -export const SHOWING_SIGNALS = '[data-test-subj="showingSignals"]'; +export const SHOWING_ALERTS = '[data-test-subj="showingAlerts"]'; -export const SIGNALS = '[data-test-subj="event"]'; +export const ALERTS = '[data-test-subj="event"]'; -export const SIGNAL_ID = '[data-test-subj="draggable-content-_id"]'; +export const ALERT_ID = '[data-test-subj="draggable-content-_id"]'; -export const SIGNAL_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; +export const ALERT_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts b/x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts similarity index 98% rename from x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts rename to x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts index 6b2c4644a95d1..9710e0e808ac5 100644 --- a/x-pack/plugins/siem/cypress/tasks/signal_detection_rules.ts +++ b/x-pack/plugins/siem/cypress/tasks/alert_detection_rules.ts @@ -24,7 +24,7 @@ import { SORT_RULES_BTN, THREE_HUNDRED_ROWS, EXPORT_ACTION_BTN, -} from '../screens/signal_detection_rules'; +} from '../screens/alert_detection_rules'; export const activateRule = (rulePosition: number) => { cy.get(RULE_SWITCH).eq(rulePosition).click({ force: true }); diff --git a/x-pack/plugins/siem/cypress/tasks/detections.ts b/x-pack/plugins/siem/cypress/tasks/detections.ts index 9461dd5ff99cf..f53dd83635d85 100644 --- a/x-pack/plugins/siem/cypress/tasks/detections.ts +++ b/x-pack/plugins/siem/cypress/tasks/detections.ts @@ -5,66 +5,66 @@ */ import { - CLOSED_SIGNALS_BTN, - EXPAND_SIGNAL_BTN, - LOADING_SIGNALS_PANEL, - MANAGE_SIGNAL_DETECTION_RULES_BTN, - OPEN_CLOSE_SIGNAL_BTN, - OPEN_CLOSE_SIGNALS_BTN, - OPENED_SIGNALS_BTN, - SEND_SIGNAL_TO_TIMELINE_BTN, - SIGNALS, - SIGNAL_CHECKBOX, + CLOSED_ALERTS_BTN, + EXPAND_ALERT_BTN, + LOADING_ALERTS_PANEL, + MANAGE_ALERT_DETECTION_RULES_BTN, + OPEN_CLOSE_ALERT_BTN, + OPEN_CLOSE_ALERTS_BTN, + OPENED_ALERTS_BTN, + SEND_ALERT_TO_TIMELINE_BTN, + ALERTS, + ALERT_CHECKBOX, } from '../screens/detections'; import { REFRESH_BUTTON } from '../screens/siem_header'; -export const closeFirstSignal = () => { - cy.get(OPEN_CLOSE_SIGNAL_BTN).first().click({ force: true }); +export const closeFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); }; -export const closeSignals = () => { - cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +export const closeAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); }; -export const expandFirstSignal = () => { - cy.get(EXPAND_SIGNAL_BTN).first().click({ force: true }); +export const expandFirstAlert = () => { + cy.get(EXPAND_ALERT_BTN).first().click({ force: true }); }; -export const goToClosedSignals = () => { - cy.get(CLOSED_SIGNALS_BTN).click({ force: true }); +export const goToClosedAlerts = () => { + cy.get(CLOSED_ALERTS_BTN).click({ force: true }); }; -export const goToManageSignalDetectionRules = () => { - cy.get(MANAGE_SIGNAL_DETECTION_RULES_BTN).should('exist').click({ force: true }); +export const goToManageAlertDetectionRules = () => { + cy.get(MANAGE_ALERT_DETECTION_RULES_BTN).should('exist').click({ force: true }); }; -export const goToOpenedSignals = () => { - cy.get(OPENED_SIGNALS_BTN).click({ force: true }); +export const goToOpenedAlerts = () => { + cy.get(OPENED_ALERTS_BTN).click({ force: true }); }; -export const openFirstSignal = () => { - cy.get(OPEN_CLOSE_SIGNAL_BTN).first().click({ force: true }); +export const openFirstAlert = () => { + cy.get(OPEN_CLOSE_ALERT_BTN).first().click({ force: true }); }; -export const openSignals = () => { - cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +export const openAlerts = () => { + cy.get(OPEN_CLOSE_ALERTS_BTN).click({ force: true }); }; -export const selectNumberOfSignals = (numberOfSignals: number) => { - for (let i = 0; i < numberOfSignals; i++) { - cy.get(SIGNAL_CHECKBOX).eq(i).click({ force: true }); +export const selectNumberOfAlerts = (numberOfAlerts: number) => { + for (let i = 0; i < numberOfAlerts; i++) { + cy.get(ALERT_CHECKBOX).eq(i).click({ force: true }); } }; -export const investigateFirstSignalInTimeline = () => { - cy.get(SEND_SIGNAL_TO_TIMELINE_BTN).first().click({ force: true }); +export const investigateFirstAlertInTimeline = () => { + cy.get(SEND_ALERT_TO_TIMELINE_BTN).first().click({ force: true }); }; -export const waitForSignals = () => { +export const waitForAlerts = () => { cy.get(REFRESH_BUTTON).invoke('text').should('not.equal', 'Updating'); }; -export const waitForSignalsIndexToBeCreated = () => { +export const waitForAlertsIndexToBeCreated = () => { cy.request({ url: '/api/detection_engine/index', retryOnStatusCodeFailure: true }).then( (response) => { if (response.status !== 200) { @@ -74,12 +74,12 @@ export const waitForSignalsIndexToBeCreated = () => { ); }; -export const waitForSignalsPanelToBeLoaded = () => { - cy.get(LOADING_SIGNALS_PANEL).should('exist'); - cy.get(LOADING_SIGNALS_PANEL).should('not.exist'); +export const waitForAlertsPanelToBeLoaded = () => { + cy.get(LOADING_ALERTS_PANEL).should('exist'); + cy.get(LOADING_ALERTS_PANEL).should('not.exist'); }; -export const waitForSignalsToBeLoaded = () => { - const expectedNumberOfDisplayedSignals = 25; - cy.get(SIGNALS).should('have.length', expectedNumberOfDisplayedSignals); +export const waitForAlertsToBeLoaded = () => { + const expectedNumberOfDisplayedAlerts = 25; + cy.get(ALERTS).should('have.length', expectedNumberOfDisplayedAlerts); }; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx similarity index 84% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx index f921c00cdafb7..7f340b0bea37b 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/signals_histogram.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/alerts_histogram.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsHistogram } from './signals_histogram'; +import { AlertsHistogram } from './alerts_histogram'; jest.mock('../../../common/lib/kibana'); -describe('SignalsHistogram', () => { +describe('AlertsHistogram', () => { it('renders correctly', () => { const wrapper = shallow( - ( +export const AlertsHistogram = React.memo( ({ chartHeight = DEFAULT_CHART_HEIGHT, data, @@ -48,9 +48,9 @@ export const SignalsHistogram = React.memo( const theme = useTheme(); const chartSize: ChartSizeArray = useMemo(() => ['100%', chartHeight], [chartHeight]); - const xAxisId = 'signalsHistogramAxisX'; - const yAxisId = 'signalsHistogramAxisY'; - const id = 'signalsHistogram'; + const xAxisId = 'alertsHistogramAxisX'; + const yAxisId = 'alertsHistogramAxisY'; + const id = 'alertsHistogram'; const yAccessors = useMemo(() => ['y'], []); const splitSeriesAccessors = useMemo(() => ['g'], []); const tickFormat = useMemo(() => histogramDateTimeFormatter([from, to]), [from, to]); @@ -59,7 +59,7 @@ export const SignalsHistogram = React.memo( <> {loading && ( ( } ); -SignalsHistogram.displayName = 'SignalsHistogram'; +AlertsHistogram.displayName = 'AlertsHistogram'; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts similarity index 88% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts index 2c5a1ddd9a010..5138835873812 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/config.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/config.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalsHistogramOption } from './types'; +import { AlertsHistogramOption } from './types'; -export const signalsHistogramOptions: SignalsHistogramOption[] = [ +export const alertsHistogramOptions: AlertsHistogramOption[] = [ { text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' }, { text: 'signal.rule.severity', value: 'signal.rule.severity' }, { text: 'signal.rule.threat.tactic.name', value: 'signal.rule.threat.tactic.name' }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx similarity index 89% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx index 2758625c0d4af..bfe4cee088a02 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.test.tsx @@ -9,25 +9,25 @@ import { showInitialLoadingSpinner } from './helpers'; describe('helpers', () => { describe('showInitialLoadingSpinner', () => { test('it should (only) show the spinner during initial loading, while we are fetching data', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: true })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: true })).toBe( true ); }); test('it should STOP showing the spinner (during initial loading) when the first data fetch completes', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: false })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingAlerts: false })).toBe( false ); }); test('it should NOT show the spinner after initial loading has completed, even if the user requests more data (e.g. by clicking Refresh)', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: true })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: true })).toBe( false ); }); test('it should NOT show the spinner after initial loading has completed', () => { - expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: false })).toBe( + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingAlerts: false })).toBe( false ); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx similarity index 67% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx index 0c9fa39e53d00..9d124201f022e 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/helpers.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/helpers.tsx @@ -5,21 +5,19 @@ */ import { showAllOthersBucket } from '../../../../common/constants'; -import { HistogramData, SignalsAggregation, SignalsBucket, SignalsGroupBucket } from './types'; -import { SignalSearchResponse } from '../../containers/detection_engine/signals/types'; +import { HistogramData, AlertsAggregation, AlertsBucket, AlertsGroupBucket } from './types'; +import { AlertSearchResponse } from '../../containers/detection_engine/alerts/types'; import * as i18n from './translations'; -export const formatSignalsData = ( - signalsData: SignalSearchResponse<{}, SignalsAggregation> | null -) => { - const groupBuckets: SignalsGroupBucket[] = - signalsData?.aggregations?.signalsByGrouping?.buckets ?? []; - return groupBuckets.reduce((acc, { key: group, signals }) => { - const signalsBucket: SignalsBucket[] = signals.buckets ?? []; +export const formatAlertsData = (alertsData: AlertSearchResponse<{}, AlertsAggregation> | null) => { + const groupBuckets: AlertsGroupBucket[] = + alertsData?.aggregations?.alertsByGrouping?.buckets ?? []; + return groupBuckets.reduce((acc, { key: group, alerts }) => { + const alertsBucket: AlertsBucket[] = alerts.buckets ?? []; return [ ...acc, - ...signalsBucket.map(({ key, doc_count }: SignalsBucket) => ({ + ...alertsBucket.map(({ key, doc_count }: AlertsBucket) => ({ x: key, y: doc_count, g: group, @@ -28,7 +26,7 @@ export const formatSignalsData = ( }, []); }; -export const getSignalsHistogramQuery = ( +export const getAlertsHistogramQuery = ( stackByField: string, from: number, to: number, @@ -44,7 +42,7 @@ export const getSignalsHistogramQuery = ( return { aggs: { - signalsByGrouping: { + alertsByGrouping: { terms: { field: stackByField, ...missing, @@ -54,7 +52,7 @@ export const getSignalsHistogramQuery = ( size: 10, }, aggs: { - signals: { + alerts: { date_histogram: { field: '@timestamp', fixed_interval: `${Math.floor((to - from) / 32)}ms`, @@ -87,15 +85,15 @@ export const getSignalsHistogramQuery = ( }; /** - * Returns `true` when the signals histogram initial loading spinner should be shown + * Returns `true` when the alerts histogram initial loading spinner should be shown * * @param isInitialLoading The loading spinner will only be displayed if this value is `true`, because after initial load, a different, non-spinner loading indicator is displayed - * @param isLoadingSignals When `true`, IO is being performed to request signals (for rendering in the histogram) + * @param isLoadingAlerts When `true`, IO is being performed to request alerts (for rendering in the histogram) */ export const showInitialLoadingSpinner = ({ isInitialLoading, - isLoadingSignals, + isLoadingAlerts, }: { isInitialLoading: boolean; - isLoadingSignals: boolean; -}): boolean => isInitialLoading && isLoadingSignals; + isLoadingAlerts: boolean; +}): boolean => isInitialLoading && isLoadingAlerts; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx index 6578af19094df..3376df76ac6ec 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/index.test.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsHistogramPanel } from './index'; +import { AlertsHistogramPanel } from './index'; jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/navigation/use_get_url_search'); -describe('SignalsHistogramPanel', () => { +describe('AlertsHistogramPanel', () => { it('renders correctly', () => { const wrapper = shallow( - ` position: relative; `; -const defaultTotalSignalsObj: SignalsTotal = { +const defaultTotalAlertsObj: AlertsTotal = { value: 0, relation: 'eq', }; export const DETECTIONS_HISTOGRAM_ID = 'detections-histogram'; -const ViewSignalsFlexItem = styled(EuiFlexItem)` +const ViewAlertsFlexItem = styled(EuiFlexItem)` margin-left: 24px; `; -interface SignalsHistogramPanelProps { +interface AlertsHistogramPanelProps { chartHeight?: number; - defaultStackByOption?: SignalsHistogramOption; + defaultStackByOption?: AlertsHistogramOption; deleteQuery?: ({ id }: { id: string }) => void; filters?: Filter[]; from: number; @@ -66,9 +66,9 @@ interface SignalsHistogramPanelProps { panelHeight?: number; signalIndexName: string | null; setQuery: (params: RegisterQuery) => void; - showLinkToSignals?: boolean; - showTotalSignalsCount?: boolean; - stackByOptions?: SignalsHistogramOption[]; + showLinkToAlerts?: boolean; + showTotalAlertsCount?: boolean; + stackByOptions?: AlertsHistogramOption[]; title?: string; to: number; updateDateRange: UpdateDateRange; @@ -81,10 +81,10 @@ const getHistogramOption = (fieldName: string): MatrixHistogramOption => ({ const NO_LEGEND_DATA: LegendItem[] = []; -export const SignalsHistogramPanel = memo( +export const AlertsHistogramPanel = memo( ({ chartHeight, - defaultStackByOption = signalsHistogramOptions[0], + defaultStackByOption = alertsHistogramOptions[0], deleteQuery, filters, headerChildren, @@ -95,8 +95,8 @@ export const SignalsHistogramPanel = memo( panelHeight = DEFAULT_PANEL_HEIGHT, setQuery, signalIndexName, - showLinkToSignals = false, - showTotalSignalsCount = false, + showLinkToAlerts = false, + showTotalAlertsCount = false, stackByOptions, to, title = i18n.HISTOGRAM_HEADER, @@ -106,32 +106,32 @@ export const SignalsHistogramPanel = memo( const uniqueQueryId = useMemo(() => `${DETECTIONS_HISTOGRAM_ID}-${uuid.v4()}`, []); const [isInitialLoading, setIsInitialLoading] = useState(true); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const [totalSignalsObj, setTotalSignalsObj] = useState(defaultTotalSignalsObj); - const [selectedStackByOption, setSelectedStackByOption] = useState( + const [totalAlertsObj, setTotalAlertsObj] = useState(defaultTotalAlertsObj); + const [selectedStackByOption, setSelectedStackByOption] = useState( onlyField == null ? defaultStackByOption : getHistogramOption(onlyField) ); const { - loading: isLoadingSignals, - data: signalsData, - setQuery: setSignalsQuery, + loading: isLoadingAlerts, + data: alertsData, + setQuery: setAlertsQuery, response, request, refetch, - } = useQuerySignals<{}, SignalsAggregation>( - getSignalsHistogramQuery(selectedStackByOption.value, from, to, []), + } = useQueryAlerts<{}, AlertsAggregation>( + getAlertsHistogramQuery(selectedStackByOption.value, from, to, []), signalIndexName ); const kibana = useKibana(); const urlSearch = useGetUrlSearch(navTabs.detections); - const totalSignals = useMemo( + const totalAlerts = useMemo( () => - i18n.SHOWING_SIGNALS( - numeral(totalSignalsObj.value).format(defaultNumberFormat), - totalSignalsObj.value, - totalSignalsObj.relation === 'gte' ? '>' : totalSignalsObj.relation === 'lte' ? '<' : '' + i18n.SHOWING_ALERTS( + numeral(totalAlertsObj.value).format(defaultNumberFormat), + totalAlertsObj.value, + totalAlertsObj.relation === 'gte' ? '>' : totalAlertsObj.relation === 'lte' ? '<' : '' ), - [totalSignalsObj] + [totalAlertsObj] ); const setSelectedOptionCallback = useCallback((event: React.ChangeEvent) => { @@ -140,12 +140,12 @@ export const SignalsHistogramPanel = memo( ); }, []); - const formattedSignalsData = useMemo(() => formatSignalsData(signalsData), [signalsData]); + const formattedAlertsData = useMemo(() => formatAlertsData(alertsData), [alertsData]); const legendItems: LegendItem[] = useMemo( () => - signalsData?.aggregations?.signalsByGrouping?.buckets != null - ? signalsData.aggregations.signalsByGrouping.buckets.map((bucket, i) => ({ + alertsData?.aggregations?.alertsByGrouping?.buckets != null + ? alertsData.aggregations.alertsByGrouping.buckets.map((bucket, i) => ({ color: i < defaultLegendColors.length ? defaultLegendColors[i] : undefined, dataProviderId: escapeDataProviderId( `draggable-legend-item-${uuid.v4()}-${selectedStackByOption.value}-${bucket.key}` @@ -154,20 +154,20 @@ export const SignalsHistogramPanel = memo( value: bucket.key, })) : NO_LEGEND_DATA, - [signalsData, selectedStackByOption.value] + [alertsData, selectedStackByOption.value] ); useEffect(() => { let canceled = false; - if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingSignals })) { + if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingAlerts })) { setIsInitialLoading(false); } return () => { canceled = true; // prevent long running data fetches from updating state after unmounting }; - }, [isInitialLoading, isLoadingSignals, setIsInitialLoading]); + }, [isInitialLoading, isLoadingAlerts, setIsInitialLoading]); useEffect(() => { return () => { @@ -185,20 +185,20 @@ export const SignalsHistogramPanel = memo( dsl: [request], response: [response], }, - loading: isLoadingSignals, + loading: isLoadingAlerts, refetch, }); } - }, [setQuery, isLoadingSignals, signalsData, response, request, refetch]); + }, [setQuery, isLoadingAlerts, alertsData, response, request, refetch]); useEffect(() => { - setTotalSignalsObj( - signalsData?.hits.total ?? { + setTotalAlertsObj( + alertsData?.hits.total ?? { value: 0, relation: 'eq', } ); - }, [signalsData]); + }, [alertsData]); useEffect(() => { const converted = esQuery.buildEsQuery( @@ -211,8 +211,8 @@ export const SignalsHistogramPanel = memo( } ); - setSignalsQuery( - getSignalsHistogramQuery( + setAlertsQuery( + getAlertsHistogramQuery( selectedStackByOption.value, from, to, @@ -222,14 +222,14 @@ export const SignalsHistogramPanel = memo( }, [selectedStackByOption.value, from, to, query, filters]); const linkButton = useMemo(() => { - if (showLinkToSignals) { + if (showLinkToAlerts) { return ( - - {i18n.VIEW_SIGNALS} - + + {i18n.VIEW_ALERTS} + ); } - }, [showLinkToSignals, urlSearch]); + }, [showLinkToAlerts, urlSearch]); const titleText = useMemo(() => (onlyField == null ? title : i18n.TOP(onlyField)), [ onlyField, @@ -237,13 +237,13 @@ export const SignalsHistogramPanel = memo( ]); return ( - + @@ -264,13 +264,13 @@ export const SignalsHistogramPanel = memo( {isInitialLoading ? ( ) : ( - @@ -281,4 +281,4 @@ export const SignalsHistogramPanel = memo( } ); -SignalsHistogramPanel.displayName = 'SignalsHistogramPanel'; +AlertsHistogramPanel.displayName = 'AlertsHistogramPanel'; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts similarity index 50% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts index e7b76a48c7592..91345e3d989f1 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/translations.ts @@ -7,116 +7,116 @@ import { i18n } from '@kbn/i18n'; export const STACK_BY_LABEL = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.stackByLabel', { defaultMessage: 'Stack by', } ); export const STACK_BY_RISK_SCORES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.riskScoresDropDown', { defaultMessage: 'Risk scores', } ); export const STACK_BY_SEVERITIES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.severitiesDropDown', { defaultMessage: 'Severities', } ); export const STACK_BY_DESTINATION_IPS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.destinationIpsDropDown', { defaultMessage: 'Top destination IPs', } ); export const STACK_BY_SOURCE_IPS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.sourceIpsDropDown', { defaultMessage: 'Top source IPs', } ); export const STACK_BY_ACTIONS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.eventActionsDropDown', { defaultMessage: 'Top event actions', } ); export const STACK_BY_CATEGORIES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.eventCategoriesDropDown', { defaultMessage: 'Top event categories', } ); export const STACK_BY_HOST_NAMES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.hostNamesDropDown', { defaultMessage: 'Top host names', } ); export const STACK_BY_RULE_TYPES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.ruleTypesDropDown', { defaultMessage: 'Top rule types', } ); export const STACK_BY_RULE_NAMES = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.rulesDropDown', { defaultMessage: 'Top rules', } ); export const STACK_BY_USERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown', + 'xpack.siem.detectionEngine.alerts.histogram.stackByOptions.usersDropDown', { defaultMessage: 'Top users', } ); export const TOP = (fieldName: string) => - i18n.translate('xpack.siem.detectionEngine.signals.histogram.topNLabel', { + i18n.translate('xpack.siem.detectionEngine.alerts.histogram.topNLabel', { values: { fieldName }, defaultMessage: `Top {fieldName}`, }); export const HISTOGRAM_HEADER = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.headerTitle', + 'xpack.siem.detectionEngine.alerts.histogram.headerTitle', { - defaultMessage: 'Signal count', + defaultMessage: 'Alert count', } ); export const ALL_OTHERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel', + 'xpack.siem.detectionEngine.alerts.histogram.allOthersGroupingLabel', { defaultMessage: 'All others', } ); -export const VIEW_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel', +export const VIEW_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.histogram.viewAlertsButtonLabel', { - defaultMessage: 'View signals', + defaultMessage: 'View alerts', } ); -export const SHOWING_SIGNALS = ( - totalSignalsFormatted: string, - totalSignals: number, +export const SHOWING_ALERTS = ( + totalAlertsFormatted: string, + totalAlerts: number, modifier: string ) => - i18n.translate('xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle', { - values: { totalSignalsFormatted, totalSignals, modifier }, + i18n.translate('xpack.siem.detectionEngine.alerts.histogram.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts, modifier }, defaultMessage: - 'Showing: {modifier}{totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', + 'Showing: {modifier}{totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts similarity index 70% rename from x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts index 41d58a4a7391d..0bf483f7ec927 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_histogram_panel/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_histogram_panel/types.ts @@ -6,7 +6,7 @@ import { inputsModel } from '../../../common/store'; -export interface SignalsHistogramOption { +export interface AlertsHistogramOption { text: string; value: string; } @@ -17,26 +17,26 @@ export interface HistogramData { g: string; } -export interface SignalsAggregation { - signalsByGrouping: { - buckets: SignalsGroupBucket[]; +export interface AlertsAggregation { + alertsByGrouping: { + buckets: AlertsGroupBucket[]; }; } -export interface SignalsBucket { +export interface AlertsBucket { key_as_string: string; key: number; doc_count: number; } -export interface SignalsGroupBucket { +export interface AlertsGroupBucket { key: string; - signals: { - buckets: SignalsBucket[]; + alerts: { + buckets: AlertsBucket[]; }; } -export interface SignalsTotal { +export interface AlertsTotal { value: number; relation: string; } diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx new file mode 100644 index 0000000000000..7d35e429bcf50 --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_info/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import React, { useState, useEffect } from 'react'; + +import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query'; +import { buildLastAlertsQuery } from './query.dsl'; +import { Aggs } from './types'; + +interface AlertInfo { + ruleId?: string | null; +} + +type Return = [React.ReactNode, React.ReactNode]; + +export const useAlertInfo = ({ ruleId = null }: AlertInfo): Return => { + const [lastAlerts, setLastAlerts] = useState( + + ); + const [totalAlerts, setTotalAlerts] = useState( + + ); + + const { loading, data: alerts } = useQueryAlerts(buildLastAlertsQuery(ruleId)); + + useEffect(() => { + if (alerts != null) { + const myAlerts = alerts; + setLastAlerts( + myAlerts.aggregations?.lastSeen.value != null ? ( + + ) : null + ); + setTotalAlerts(<>{myAlerts.hits.total.value}); + } else { + setLastAlerts(null); + setTotalAlerts(null); + } + }, [loading, alerts]); + + return [lastAlerts, totalAlerts]; +}; diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts b/x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts similarity index 91% rename from x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts index 8cb07a4f8e6b5..a3972fd35bf2d 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals_info/query.dsl.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_info/query.dsl.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const buildLastSignalsQuery = (ruleId: string | undefined | null) => { +export const buildLastAlertsQuery = (ruleId: string | undefined | null) => { const queryFilter = [ { bool: { should: [{ match: { 'signal.status': 'open' } }], minimum_should_match: 1 }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_info/types.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals_info/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_info/types.ts diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx similarity index 92% rename from x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx index d7a8a55077340..2fa7cfeedcd15 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.test.tsx @@ -6,9 +6,9 @@ import sinon from 'sinon'; import moment from 'moment'; -import { sendSignalToTimelineAction, determineToAndFrom } from './actions'; +import { sendAlertToTimelineAction, determineToAndFrom } from './actions'; import { - mockEcsDataWithSignal, + mockEcsDataWithAlert, defaultTimelineProps, apolloClient, mockTimelineApolloResult, @@ -19,7 +19,7 @@ import { TimelineType, TimelineStatus } from '../../../../common/types/timeline' jest.mock('apollo-client'); -describe('signals actions', () => { +describe('alert actions', () => { const anchor = '2020-03-01T17:59:46.349Z'; const unix = moment(anchor).valueOf(); let createTimeline: CreateTimeline; @@ -46,13 +46,13 @@ describe('signals actions', () => { clock.restore(); }); - describe('sendSignalToTimelineAction', () => { + describe('sendAlertToTimelineAction', () => { describe('timeline id is NOT empty string and apollo client exists', () => { test('it invokes updateTimelineIsLoading to set to true', async () => { - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); @@ -61,10 +61,10 @@ describe('signals actions', () => { }); test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => { - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); const expected = { @@ -246,10 +246,10 @@ describe('signals actions', () => { }; jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); // @ts-ignore @@ -275,10 +275,10 @@ describe('signals actions', () => { }; jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResultModified); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); // @ts-ignore @@ -293,10 +293,10 @@ describe('signals actions', () => { throw new Error('Test error'); }); - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, updateTimelineIsLoading, }); @@ -313,16 +313,16 @@ describe('signals actions', () => { describe('timelineId is empty string', () => { test('it invokes createTimeline with timelineDefaults', async () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, signal: { rule: { - ...mockEcsDataWithSignal.signal?.rule!, + ...mockEcsDataWithAlert.signal?.rule!, timeline_id: null, }, }, }; - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ apolloClient, createTimeline, ecsData: ecsDataMock, @@ -338,16 +338,16 @@ describe('signals actions', () => { describe('apolloClient is not defined', () => { test('it invokes createTimeline with timelineDefaults', async () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, signal: { rule: { - ...mockEcsDataWithSignal.signal?.rule!, + ...mockEcsDataWithAlert.signal?.rule!, timeline_id: [''], }, }, }; - await sendSignalToTimelineAction({ + await sendAlertToTimelineAction({ createTimeline, ecsData: ecsDataMock, updateTimelineIsLoading, @@ -363,7 +363,7 @@ describe('signals actions', () => { describe('determineToAndFrom', () => { test('it uses ecs.Data.timestamp if one is provided', () => { const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, timestamp: '2020-03-20T17:59:46.349Z', }; const result = determineToAndFrom({ ecsData: ecsDataMock }); @@ -374,7 +374,7 @@ describe('signals actions', () => { test('it uses current time timestamp if ecsData.timestamp is not provided', () => { const { timestamp, ...ecsDataMock } = { - ...mockEcsDataWithSignal, + ...mockEcsDataWithAlert, }; const result = determineToAndFrom({ ecsData: ecsDataMock }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx similarity index 82% rename from x-pack/plugins/siem/public/alerts/components/signals/actions.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx index f01b39ccaba0d..cde81d44bc5d6 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/actions.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/actions.tsx @@ -8,8 +8,8 @@ import dateMath from '@elastic/datemath'; import { getOr, isEmpty } from 'lodash/fp'; import moment from 'moment'; -import { updateSignalStatus } from '../../containers/detection_engine/signals/api'; -import { SendSignalToTimelineActionProps, UpdateSignalStatusActionProps } from './types'; +import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; +import { SendAlertToTimelineActionProps, UpdateAlertStatusActionProps } from './types'; import { TimelineNonEcsData, GetOneTimeline, TimelineResult, Ecs } from '../../../graphql/types'; import { oneTimelineQuery } from '../../../timelines/containers/one/index.gql_query'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; @@ -24,7 +24,7 @@ import { replaceTemplateFieldFromDataProviders, } from './helpers'; -export const getUpdateSignalsQuery = (eventIds: Readonly) => { +export const getUpdateAlertsQuery = (eventIds: Readonly) => { return { query: { bool: { @@ -44,35 +44,35 @@ export const getFilterAndRuleBounds = ( const stringFilter = data?.[0].filter((d) => d.field === 'signal.rule.filters')?.[0]?.value ?? []; const eventTimes = data - .flatMap((signal) => signal.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) + .flatMap((alert) => alert.filter((d) => d.field === 'signal.original_time')?.[0]?.value ?? []) .map((d) => moment(d)); return [stringFilter, moment.min(eventTimes).valueOf(), moment.max(eventTimes).valueOf()]; }; -export const updateSignalStatusAction = async ({ +export const updateAlertStatusAction = async ({ query, - signalIds, + alertIds, status, setEventsLoading, setEventsDeleted, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, -}: UpdateSignalStatusActionProps) => { +}: UpdateAlertStatusActionProps) => { try { - setEventsLoading({ eventIds: signalIds, isLoading: true }); + setEventsLoading({ eventIds: alertIds, isLoading: true }); - const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); + const queryObject = query ? { query: JSON.parse(query) } : getUpdateAlertsQuery(alertIds); - const response = await updateSignalStatus({ query: queryObject, status }); + const response = await updateAlertStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules - setEventsDeleted({ eventIds: signalIds, isDeleted: true }); + setEventsDeleted({ eventIds: alertIds, isDeleted: true }); onAlertStatusUpdateSuccess(response.updated, status); } catch (error) { onAlertStatusUpdateFailure(status, error); } finally { - setEventsLoading({ eventIds: signalIds, isLoading: false }); + setEventsLoading({ eventIds: alertIds, isLoading: false }); } }; @@ -91,13 +91,13 @@ export const determineToAndFrom = ({ ecsData }: { ecsData: Ecs }) => { return { to, from }; }; -export const sendSignalToTimelineAction = async ({ +export const sendAlertToTimelineAction = async ({ apolloClient, createTimeline, ecsData, updateTimelineIsLoading, -}: SendSignalToTimelineActionProps) => { - let openSignalInBasicTimeline = true; +}: SendAlertToTimelineActionProps) => { + let openAlertInBasicTimeline = true; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; @@ -120,7 +120,7 @@ export const sendSignalToTimelineAction = async ({ if (!isEmpty(resultingTimeline)) { const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); - openSignalInBasicTimeline = false; + openAlertInBasicTimeline = false; const { timeline } = formatTimelineResultToModel(timelineTemplate, true); const query = replaceTemplateFieldFromQuery( timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', @@ -162,12 +162,12 @@ export const sendSignalToTimelineAction = async ({ }); } } catch { - openSignalInBasicTimeline = true; + openAlertInBasicTimeline = true; updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); } } - if (openSignalInBasicTimeline) { + if (openAlertInBasicTimeline) { createTimeline({ from, timeline: { @@ -175,7 +175,7 @@ export const sendSignalToTimelineAction = async ({ dataProviders: [ { and: [], - id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-${ecsData._id}`, + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-${ecsData._id}`, name: ecsData._id, enabled: true, excluded: false, diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx similarity index 68% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx index dd30bb1b0a74d..d7fabdabf8225 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.test.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsTableFilterGroup } from './index'; +import { AlertsTableFilterGroup } from './index'; -describe('SignalsTableFilterGroup', () => { +describe('AlertsTableFilterGroup', () => { it('renders correctly', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('EuiFilterButton')).toBeTruthy(); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx similarity index 74% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx index a8dd22863e3c9..8521170637d6f 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_filter_group/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_filter_group/index.tsx @@ -10,13 +10,13 @@ import * as i18n from '../translations'; export const FILTER_OPEN = 'open'; export const FILTER_CLOSED = 'closed'; -export type SignalFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; +export type AlertFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; interface Props { - onFilterGroupChanged: (filterGroup: SignalFilterOption) => void; + onFilterGroupChanged: (filterGroup: AlertFilterOption) => void; } -const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { +const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const onClickOpenFilterCallback = useCallback(() => { @@ -32,23 +32,23 @@ const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChange return ( - {i18n.OPEN_SIGNALS} + {i18n.OPEN_ALERTS} - {i18n.CLOSED_SIGNALS} + {i18n.CLOSED_ALERTS} ); }; -export const SignalsTableFilterGroup = React.memo(SignalsTableFilterGroupComponent); +export const AlertsTableFilterGroup = React.memo(AlertsTableFilterGroupComponent); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx similarity index 76% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx index 3b43185c2c16b..543e11c9b1e69 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsUtilityBar } from './index'; +import { AlertsUtilityBar } from './index'; jest.mock('../../../../common/lib/kibana'); -describe('SignalsUtilityBar', () => { +describe('AlertsUtilityBar', () => { it('renders correctly', () => { const wrapper = shallow( - { isFilteredToOpen={false} selectAll={jest.fn()} showClearSelection={true} - updateSignalsStatus={jest.fn()} + updateAlertsStatus={jest.fn()} /> ); - expect(wrapper.find('[dataTestSubj="openCloseSignal"]')).toBeTruthy(); + expect(wrapper.find('[dataTestSubj="openCloseAlert"]')).toBeTruthy(); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx similarity index 77% rename from x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx index e23f4ebdd3d30..68b7039690db4 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/index.tsx @@ -19,10 +19,10 @@ import { import * as i18n from './translations'; import { useUiSetting$ } from '../../../../common/lib/kibana'; import { TimelineNonEcsData } from '../../../../graphql/types'; -import { UpdateSignalsStatus } from '../types'; -import { FILTER_CLOSED, FILTER_OPEN } from '../signals_filter_group'; +import { UpdateAlertsStatus } from '../types'; +import { FILTER_CLOSED, FILTER_OPEN } from '../alerts_filter_group'; -interface SignalsUtilityBarProps { +interface AlertsUtilityBarProps { canUserCRUD: boolean; hasIndexWrite: boolean; areEventsLoading: boolean; @@ -32,10 +32,10 @@ interface SignalsUtilityBarProps { selectedEventIds: Readonly>; showClearSelection: boolean; totalCount: number; - updateSignalsStatus: UpdateSignalsStatus; + updateAlertsStatus: UpdateAlertsStatus; } -const SignalsUtilityBarComponent: React.FC = ({ +const AlertsUtilityBarComponent: React.FC = ({ canUserCRUD, hasIndexWrite, areEventsLoading, @@ -45,16 +45,16 @@ const SignalsUtilityBarComponent: React.FC = ({ isFilteredToOpen, selectAll, showClearSelection, - updateSignalsStatus, + updateAlertsStatus, }) => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); const handleUpdateStatus = useCallback(async () => { - await updateSignalsStatus({ - signalIds: Object.keys(selectedEventIds), + await updateAlertsStatus({ + alertIds: Object.keys(selectedEventIds), status: isFilteredToOpen ? FILTER_CLOSED : FILTER_OPEN, }); - }, [selectedEventIds, updateSignalsStatus, isFilteredToOpen]); + }, [selectedEventIds, updateAlertsStatus, isFilteredToOpen]); const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat); const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format( @@ -66,25 +66,25 @@ const SignalsUtilityBarComponent: React.FC = ({ - - {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} + + {i18n.SHOWING_ALERTS(formattedTotalCount, totalCount)} {canUserCRUD && hasIndexWrite && ( <> - - {i18n.SELECTED_SIGNALS( + + {i18n.SELECTED_ALERTS( showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, showClearSelection ? totalCount : Object.keys(selectedEventIds).length )} {isFilteredToOpen @@ -104,7 +104,7 @@ const SignalsUtilityBarComponent: React.FC = ({ > {showClearSelection ? i18n.CLEAR_SELECTION - : i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)} + : i18n.SELECT_ALL_ALERTS(formattedTotalCount, totalCount)} )} @@ -115,8 +115,8 @@ const SignalsUtilityBarComponent: React.FC = ({ ); }; -export const SignalsUtilityBar = React.memo( - SignalsUtilityBarComponent, +export const AlertsUtilityBar = React.memo( + AlertsUtilityBarComponent, (prevProps, nextProps) => prevProps.areEventsLoading === nextProps.areEventsLoading && prevProps.selectedEventIds === nextProps.selectedEventIds && diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts new file mode 100644 index 0000000000000..ae5070efc21e1 --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/alerts_utility_bar/translations.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SHOWING_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.showingAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Showing {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECTED_ALERTS = (selectedAlertsFormatted: string, selectedAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.selectedAlertsTitle', { + values: { selectedAlertsFormatted, selectedAlerts }, + defaultMessage: + 'Selected {selectedAlertsFormatted} {selectedAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const SELECT_ALL_ALERTS = (totalAlertsFormatted: string, totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.utilityBar.selectAllAlertsTitle', { + values: { totalAlertsFormatted, totalAlerts }, + defaultMessage: + 'Select all {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const CLEAR_SELECTION = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.clearSelectionTitle', + { + defaultMessage: 'Clear selection', + } +); + +export const BATCH_ACTIONS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActionsTitle', + { + defaultMessage: 'Batch actions', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInHostsTitle', + { + defaultMessage: 'View selected in hosts', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInNetworkTitle', + { + defaultMessage: 'View selected in network', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.viewSelectedInTimelineTitle', + { + defaultMessage: 'View selected in timeline', + } +); + +export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.openSelectedTitle', + { + defaultMessage: 'Open selected', + } +); + +export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.alerts.utilityBar.batchActions.closeSelectedTitle', + { + defaultMessage: 'Close selected', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx similarity index 67% rename from x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx index 7821bfaaf9575..b191464984c53 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.test.tsx @@ -9,23 +9,23 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; import { TimelineAction } from '../../../timelines/components/timeline/body/actions'; -import { buildSignalsRuleIdFilter, getSignalsActions } from './default_config'; +import { buildAlertsRuleIdFilter, getAlertActions } from './default_config'; import { CreateTimeline, SetEventsDeletedProps, SetEventsLoadingProps, UpdateTimelineLoading, } from './types'; -import { mockEcsDataWithSignal } from '../../../common/mock/mock_ecs'; -import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions'; +import { mockEcsDataWithAlert } from '../../../common/mock/mock_ecs'; +import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; import * as i18n from './translations'; jest.mock('./actions'); -describe('signals default_config', () => { - describe('buildSignalsRuleIdFilter', () => { +describe('alerts default_config', () => { + describe('buildAlertsRuleIdFilter', () => { test('given a rule id this will return an array with a single filter', () => { - const filters: Filter[] = buildSignalsRuleIdFilter('rule-id-1'); + const filters: Filter[] = buildAlertsRuleIdFilter('rule-id-1'); const expectedFilter: Filter = { meta: { alias: null, @@ -48,7 +48,7 @@ describe('signals default_config', () => { }); }); - describe('getSignalsActions', () => { + describe('getAlertActions', () => { let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; let setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; let createTimeline: CreateTimeline; @@ -67,8 +67,8 @@ describe('signals default_config', () => { }); describe('timeline tooltip', () => { - test('it invokes sendSignalToTimelineAction when button clicked', () => { - const signalsActions = getSignalsActions({ + test('it invokes sendAlertToTimelineAction when button clicked', () => { + const alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -79,24 +79,24 @@ describe('signals default_config', () => { onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, }); - const timelineAction = signalsActions[0].getAction({ + const timelineAction = alertsActions[0].getAction({ eventId: 'even-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); const wrapper = mount(timelineAction as React.ReactElement); wrapper.find(EuiButtonIcon).simulate('click'); - expect(sendSignalToTimelineAction).toHaveBeenCalled(); + expect(sendAlertToTimelineAction).toHaveBeenCalled(); }); }); - describe('signal open action', () => { - let signalsActions: TimelineAction[]; - let signalOpenAction: JSX.Element; + describe('alert open action', () => { + let alertsActions: TimelineAction[]; + let alertOpenAction: JSX.Element; let wrapper: ReactWrapper; beforeEach(() => { - signalsActions = getSignalsActions({ + alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -108,23 +108,23 @@ describe('signals default_config', () => { onAlertStatusUpdateFailure, }); - signalOpenAction = signalsActions[1].getAction({ + alertOpenAction = alertsActions[1].getAction({ eventId: 'event-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); - wrapper = mount(signalOpenAction as React.ReactElement); + wrapper = mount(alertOpenAction as React.ReactElement); }); afterEach(() => { wrapper.unmount(); }); - test('it invokes updateSignalStatusAction when button clicked', () => { + test('it invokes updateAlertStatusAction when button clicked', () => { wrapper.find(EuiButtonIcon).simulate('click'); - expect(updateSignalStatusAction).toHaveBeenCalledWith({ - signalIds: ['event-id'], + expect(updateAlertStatusAction).toHaveBeenCalledWith({ + alertIds: ['event-id'], status: 'open', setEventsLoading, setEventsDeleted, @@ -134,27 +134,27 @@ describe('signals default_config', () => { }); test('it displays expected text on hover', () => { - const openSignal = wrapper.find(EuiToolTip); - openSignal.simulate('mouseOver'); + const openAlert = wrapper.find(EuiToolTip); + openAlert.simulate('mouseOver'); const tooltip = wrapper.find('.euiToolTipPopover').text(); - expect(tooltip).toEqual(i18n.ACTION_OPEN_SIGNAL); + expect(tooltip).toEqual(i18n.ACTION_OPEN_ALERT); }); test('it displays expected icon', () => { const icon = wrapper.find(EuiButtonIcon).props().iconType; - expect(icon).toEqual('securitySignalDetected'); + expect(icon).toEqual('securityAlertDetected'); }); }); - describe('signal close action', () => { - let signalsActions: TimelineAction[]; - let signalCloseAction: JSX.Element; + describe('alert close action', () => { + let alertsActions: TimelineAction[]; + let alertCloseAction: JSX.Element; let wrapper: ReactWrapper; beforeEach(() => { - signalsActions = getSignalsActions({ + alertsActions = getAlertActions({ canUserCRUD: true, hasIndexWrite: true, setEventsLoading, @@ -166,23 +166,23 @@ describe('signals default_config', () => { onAlertStatusUpdateFailure, }); - signalCloseAction = signalsActions[1].getAction({ + alertCloseAction = alertsActions[1].getAction({ eventId: 'event-id', - ecsData: mockEcsDataWithSignal, + ecsData: mockEcsDataWithAlert, }); - wrapper = mount(signalCloseAction as React.ReactElement); + wrapper = mount(alertCloseAction as React.ReactElement); }); afterEach(() => { wrapper.unmount(); }); - test('it invokes updateSignalStatusAction when status button clicked', () => { + test('it invokes updateAlertStatusAction when status button clicked', () => { wrapper.find(EuiButtonIcon).simulate('click'); - expect(updateSignalStatusAction).toHaveBeenCalledWith({ - signalIds: ['event-id'], + expect(updateAlertStatusAction).toHaveBeenCalledWith({ + alertIds: ['event-id'], status: 'closed', setEventsLoading, setEventsDeleted, @@ -192,16 +192,16 @@ describe('signals default_config', () => { }); test('it displays expected text on hover', () => { - const closeSignal = wrapper.find(EuiToolTip); - closeSignal.simulate('mouseOver'); + const closeAlert = wrapper.find(EuiToolTip); + closeAlert.simulate('mouseOver'); const tooltip = wrapper.find('.euiToolTipPopover').text(); - expect(tooltip).toEqual(i18n.ACTION_CLOSE_SIGNAL); + expect(tooltip).toEqual(i18n.ACTION_CLOSE_ALERT); }); test('it displays expected icon', () => { const icon = wrapper.find(EuiButtonIcon).props().iconType; - expect(icon).toEqual('securitySignalResolved'); + expect(icon).toEqual('securityAlertResolved'); }); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx similarity index 81% rename from x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx index 1269f31064e9e..6cef2e7c53c46 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/default_config.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/default_config.tsx @@ -23,8 +23,8 @@ import { import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { FILTER_OPEN } from './signals_filter_group'; -import { sendSignalToTimelineAction, updateSignalStatusAction } from './actions'; +import { FILTER_OPEN } from './alerts_filter_group'; +import { sendAlertToTimelineAction, updateAlertStatusAction } from './actions'; import * as i18n from './translations'; import { CreateTimeline, @@ -33,7 +33,7 @@ import { UpdateTimelineLoading, } from './types'; -export const signalsOpenFilters: Filter[] = [ +export const alertsOpenFilters: Filter[] = [ { meta: { alias: null, @@ -53,7 +53,7 @@ export const signalsOpenFilters: Filter[] = [ }, ]; -export const signalsClosedFilters: Filter[] = [ +export const alertsClosedFilters: Filter[] = [ { meta: { alias: null, @@ -73,7 +73,7 @@ export const signalsClosedFilters: Filter[] = [ }, ]; -export const buildSignalsRuleIdFilter = (ruleId: string): Filter[] => [ +export const buildAlertsRuleIdFilter = (ruleId: string): Filter[] => [ { meta: { alias: null, @@ -93,7 +93,7 @@ export const buildSignalsRuleIdFilter = (ruleId: string): Filter[] => [ }, ]; -export const signalsHeaders: ColumnHeaderOptions[] = [ +export const alertsHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', @@ -102,32 +102,32 @@ export const signalsHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.name', - label: i18n.SIGNALS_HEADERS_RULE, + label: i18n.ALERTS_HEADERS_RULE, linkField: 'signal.rule.id', width: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.version', - label: i18n.SIGNALS_HEADERS_VERSION, + label: i18n.ALERTS_HEADERS_VERSION, width: 100, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.type', - label: i18n.SIGNALS_HEADERS_METHOD, + label: i18n.ALERTS_HEADERS_METHOD, width: 100, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.severity', - label: i18n.SIGNALS_HEADERS_SEVERITY, + label: i18n.ALERTS_HEADERS_SEVERITY, width: 105, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.risk_score', - label: i18n.SIGNALS_HEADERS_RISK_SCORE, + label: i18n.ALERTS_HEADERS_RISK_SCORE, width: 115, }, { @@ -171,9 +171,9 @@ export const signalsHeaders: ColumnHeaderOptions[] = [ }, ]; -export const signalsDefaultModel: SubsetTimelineModel = { +export const alertsDefaultModel: SubsetTimelineModel = { ...timelineDefaults, - columns: signalsHeaders, + columns: alertsHeaders, showCheckboxes: true, showRowRenderers: false, }; @@ -189,7 +189,7 @@ export const requiredFieldsForActions = [ 'signal.rule.id', ]; -export const getSignalsActions = ({ +export const getAlertActions = ({ apolloClient, canUserCRUD, hasIndexWrite, @@ -215,13 +215,13 @@ export const getSignalsActions = ({ { getAction: ({ ecsData }: TimelineActionProps): JSX.Element => ( - sendSignalToTimelineAction({ + sendAlertToTimelineAction({ apolloClient, createTimeline, ecsData, @@ -233,20 +233,20 @@ export const getSignalsActions = ({ /> ), - id: 'sendSignalToTimeline', + id: 'sendAlertToTimeline', width: 26, }, { getAction: ({ eventId }: TimelineActionProps): JSX.Element => ( - updateSignalStatusAction({ - signalIds: [eventId], + updateAlertStatusAction({ + alertIds: [eventId], status, setEventsLoading, setEventsDeleted, @@ -255,12 +255,12 @@ export const getSignalsActions = ({ }) } isDisabled={!canUserCRUD || !hasIndexWrite} - iconType={status === FILTER_OPEN ? 'securitySignalDetected' : 'securitySignalResolved'} + iconType={status === FILTER_OPEN ? 'securityAlertDetected' : 'securityAlertResolved'} aria-label="Next" /> ), - id: 'updateSignalStatus', + id: 'updateAlertStatus', width: 26, }, ]; diff --git a/x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.test.ts similarity index 100% rename from x-pack/plugins/siem/public/alerts/components/signals/helpers.test.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.test.ts diff --git a/x-pack/plugins/siem/public/alerts/components/signals/helpers.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts similarity index 98% rename from x-pack/plugins/siem/public/alerts/components/signals/helpers.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts index e9b8fdda84053..11a03b0426891 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/helpers.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/helpers.ts @@ -19,7 +19,7 @@ interface FindValueToChangeInQuery { /** * Fields that will be replaced with the template strings from a a saved timeline template. - * This is used for the signals detection engine feature when you save a timeline template + * This is used for the alerts detection engine feature when you save a timeline template * and are the fields you can replace when creating a template. */ const templateFields = [ diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx similarity index 85% rename from x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx index b66a9fc881045..51fdd828bcddb 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.test.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SignalsTableComponent } from './index'; +import { AlertsTableComponent } from './index'; -describe('SignalsTableComponent', () => { +describe('AlertsTableComponent', () => { it('renders correctly', () => { const wrapper = shallow( - { /> ); - expect(wrapper.find('[title="Signals"]')).toBeTruthy(); + expect(wrapper.find('[title="Alerts"]')).toBeTruthy(); }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx similarity index 81% rename from x-pack/plugins/siem/public/alerts/components/signals/index.tsx rename to x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx index effb6a2450dc2..05c811d8e19bd 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/index.tsx @@ -22,28 +22,28 @@ import { TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { useApolloClient } from '../../../common/utils/apollo_context'; -import { updateSignalStatusAction } from './actions'; +import { updateAlertStatusAction } from './actions'; import { - getSignalsActions, + getAlertActions, requiredFieldsForActions, - signalsClosedFilters, - signalsDefaultModel, - signalsOpenFilters, + alertsClosedFilters, + alertsDefaultModel, + alertsOpenFilters, } from './default_config'; import { FILTER_CLOSED, FILTER_OPEN, - SignalFilterOption, - SignalsTableFilterGroup, -} from './signals_filter_group'; -import { SignalsUtilityBar } from './signals_utility_bar'; + AlertFilterOption, + AlertsTableFilterGroup, +} from './alerts_filter_group'; +import { AlertsUtilityBar } from './alerts_utility_bar'; import * as i18n from './translations'; import { CreateTimelineProps, SetEventsDeletedProps, SetEventsLoadingProps, - UpdateSignalsStatusCallback, - UpdateSignalsStatusProps, + UpdateAlertsStatusCallback, + UpdateAlertsStatusProps, } from './types'; import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; import { @@ -52,7 +52,7 @@ import { displayErrorToast, } from '../../../common/components/toasters'; -export const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; +export const ALERTS_TABLE_TIMELINE_ID = 'alerts-table'; interface OwnProps { canUserCRUD: boolean; @@ -64,9 +64,9 @@ interface OwnProps { to: number; } -type SignalsTableComponentProps = OwnProps & PropsFromRedux; +type AlertsTableComponentProps = OwnProps & PropsFromRedux; -export const SignalsTableComponent: React.FC = ({ +export const AlertsTableComponent: React.FC = ({ canUserCRUD, clearEventsDeleted, clearEventsLoading, @@ -91,7 +91,7 @@ export const SignalsTableComponent: React.FC = ({ const apolloClient = useApolloClient(); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( signalsIndex !== '' ? [signalsIndex] : [] ); @@ -140,16 +140,16 @@ export const SignalsTableComponent: React.FC = ({ const setEventsLoadingCallback = useCallback( ({ eventIds, isLoading }: SetEventsLoadingProps) => { - setEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isLoading }); + setEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isLoading }); }, - [setEventsLoading, SIGNALS_PAGE_TIMELINE_ID] + [setEventsLoading, ALERTS_TABLE_TIMELINE_ID] ); const setEventsDeletedCallback = useCallback( ({ eventIds, isDeleted }: SetEventsDeletedProps) => { - setEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isDeleted }); + setEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isDeleted }); }, - [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] + [setEventsDeleted, ALERTS_TABLE_TIMELINE_ID] ); const onAlertStatusUpdateSuccess = useCallback( @@ -184,10 +184,10 @@ export const SignalsTableComponent: React.FC = ({ // Callback for when open/closed filter changes const onFilterGroupChangedCallback = useCallback( - (newFilterGroup: SignalFilterOption) => { - clearEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + (newFilterGroup: AlertFilterOption) => { + clearEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); setFilterGroup(newFilterGroup); }, [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] @@ -195,7 +195,7 @@ export const SignalsTableComponent: React.FC = ({ // Callback for clearing entire selection from utility bar const clearSelectionCallback = useCallback(() => { - clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); setSelectAll(false); setShowClearSelectionAction(false); }, [clearSelected, setSelectAll, setShowClearSelectionAction]); @@ -208,11 +208,11 @@ export const SignalsTableComponent: React.FC = ({ setShowClearSelectionAction(true); }, [setSelectAll, setShowClearSelectionAction]); - const updateSignalsStatusCallback: UpdateSignalsStatusCallback = useCallback( - async (refetchQuery: inputsModel.Refetch, { signalIds, status }: UpdateSignalsStatusProps) => { - await updateSignalStatusAction({ + const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback( + async (refetchQuery: inputsModel.Refetch, { alertIds, status }: UpdateAlertsStatusProps) => { + await updateAlertStatusAction({ query: showClearSelectionAction ? getGlobalQuery()?.filterQuery : undefined, - signalIds: Object.keys(selectedEventIds), + alertIds: Object.keys(selectedEventIds), status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, @@ -232,11 +232,11 @@ export const SignalsTableComponent: React.FC = ({ ] ); - // Callback for creating the SignalUtilityBar which receives totalCount from EventsViewer component + // Callback for creating the AlertsUtilityBar which receives totalCount from EventsViewer component const utilityBarCallback = useCallback( (refetchQuery: inputsModel.Refetch, totalCount: number) => { return ( - 0} clearSelection={clearSelectionCallback} @@ -246,7 +246,7 @@ export const SignalsTableComponent: React.FC = ({ selectedEventIds={selectedEventIds} showClearSelection={showClearSelectionAction} totalCount={totalCount} - updateSignalsStatus={updateSignalsStatusCallback.bind(null, refetchQuery)} + updateAlertsStatus={updateAlertsStatusCallback.bind(null, refetchQuery)} /> ); }, @@ -259,14 +259,14 @@ export const SignalsTableComponent: React.FC = ({ selectAllCallback, selectedEventIds, showClearSelectionAction, - updateSignalsStatusCallback, + updateAlertsStatusCallback, ] ); - // Send to Timeline / Update Signal Status Actions for each table row + // Send to Timeline / Update Alert Status Actions for each table row const additionalActions = useMemo( () => - getSignalsActions({ + getAlertActions({ apolloClient, canUserCRUD, hasIndexWrite, @@ -295,38 +295,38 @@ export const SignalsTableComponent: React.FC = ({ const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo(() => { if (isEmpty(defaultFilters)) { - return filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters; + return filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters; } else if (defaultFilters != null && !isEmpty(defaultFilters)) { return [ ...defaultFilters, - ...(filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters), + ...(filterGroup === FILTER_OPEN ? alertsOpenFilters : alertsClosedFilters), ]; } }, [defaultFilters, filterGroup]); const timelineTypeContext = useMemo( () => ({ - documentType: i18n.SIGNALS_DOCUMENT_TYPE, - footerText: i18n.TOTAL_COUNT_OF_SIGNALS, - loadingText: i18n.LOADING_SIGNALS, + documentType: i18n.ALERTS_DOCUMENT_TYPE, + footerText: i18n.TOTAL_COUNT_OF_ALERTS, + loadingText: i18n.LOADING_ALERTS, queryFields: requiredFieldsForActions, timelineActions: additionalActions, - title: i18n.SIGNALS_TABLE_TITLE, + title: i18n.ALERTS_TABLE_TITLE, selectAll: canUserCRUD ? selectAll : false, }), [additionalActions, canUserCRUD, selectAll] ); const headerFilterGroup = useMemo( - () => , + () => , [onFilterGroupChangedCallback] ); if (loading || isEmpty(signalsIndex)) { return ( - - + + ); } @@ -335,10 +335,10 @@ export const SignalsTableComponent: React.FC = ({ { const getGlobalInputs = inputsSelectors.globalSelector(); const mapStateToProps = (state: State) => { const timeline: TimelineModel = - getTimeline(state, SIGNALS_PAGE_TIMELINE_ID) ?? timelineDefaults; + getTimeline(state, ALERTS_TABLE_TIMELINE_ID) ?? timelineDefaults; const { deletedEventIds, isSelectAllChecked, loadingEventIds, selectedEventIds } = timeline; const globalInputs: inputsModel.InputsRange = getGlobalInputs(state); @@ -401,4 +401,4 @@ const connector = connect(makeMapStateToProps, mapDispatchToProps); type PropsFromRedux = ConnectedProps; -export const SignalsTable = connector(React.memo(SignalsTableComponent)); +export const AlertsTable = connector(React.memo(AlertsTableComponent)); diff --git a/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts new file mode 100644 index 0000000000000..4f34e9d031eed --- /dev/null +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/translations.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { + defaultMessage: 'Detection engine', +}); + +export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.alerts.tableTitle', { + defaultMessage: 'Alert list', +}); + +export const ALERTS_DOCUMENT_TYPE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.documentTypeTitle', + { + defaultMessage: 'Alerts', + } +); + +export const OPEN_ALERTS = i18n.translate('xpack.siem.detectionEngine.alerts.openAlertsTitle', { + defaultMessage: 'Open alerts', +}); + +export const CLOSED_ALERTS = i18n.translate('xpack.siem.detectionEngine.alerts.closedAlertsTitle', { + defaultMessage: 'Closed alerts', +}); + +export const LOADING_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.loadingAlertsTitle', + { + defaultMessage: 'Loading Alerts', + } +); + +export const TOTAL_COUNT_OF_ALERTS = i18n.translate( + 'xpack.siem.detectionEngine.alerts.totalCountOfAlertsTitle', + { + defaultMessage: 'alerts match the search criteria', + } +); + +export const ALERTS_HEADERS_RULE = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.ruleTitle', + { + defaultMessage: 'Rule', + } +); + +export const ALERTS_HEADERS_VERSION = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.versionTitle', + { + defaultMessage: 'Version', + } +); + +export const ALERTS_HEADERS_METHOD = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.methodTitle', + { + defaultMessage: 'Method', + } +); + +export const ALERTS_HEADERS_SEVERITY = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.severityTitle', + { + defaultMessage: 'Severity', + } +); + +export const ALERTS_HEADERS_RISK_SCORE = i18n.translate( + 'xpack.siem.eventsViewer.alerts.defaultHeaders.riskScoreTitle', + { + defaultMessage: 'Risk Score', + } +); + +export const ACTION_OPEN_ALERT = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.openAlertTitle', + { + defaultMessage: 'Open alert', + } +); + +export const ACTION_CLOSE_ALERT = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.closeAlertTitle', + { + defaultMessage: 'Close alert', + } +); + +export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.alerts.actions.investigateInTimelineTitle', + { + defaultMessage: 'Investigate in timeline', + } +); + +export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.closedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.siem.detectionEngine.alerts.openedAlertSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.alerts.closedAlertFailedToastMessage', + { + defaultMessage: 'Failed to close alert(s).', + } +); + +export const OPENED_ALERT_FAILED_TOAST = i18n.translate( + 'xpack.siem.detectionEngine.alerts.openedAlertFailedToastMessage', + { + defaultMessage: 'Failed to open alert(s)', + } +); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/types.ts b/x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts similarity index 77% rename from x-pack/plugins/siem/public/alerts/components/signals/types.ts rename to x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts index 542aa61074ce1..ba342ae441857 100644 --- a/x-pack/plugins/siem/public/alerts/components/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/components/alerts_table/types.ts @@ -20,20 +20,20 @@ export interface SetEventsDeletedProps { isDeleted: boolean; } -export interface UpdateSignalsStatusProps { - signalIds: string[]; +export interface UpdateAlertsStatusProps { + alertIds: string[]; status: 'open' | 'closed'; } -export type UpdateSignalsStatusCallback = ( +export type UpdateAlertsStatusCallback = ( refetchQuery: inputsModel.Refetch, - { signalIds, status }: UpdateSignalsStatusProps + { alertIds, status }: UpdateAlertsStatusProps ) => void; -export type UpdateSignalsStatus = ({ signalIds, status }: UpdateSignalsStatusProps) => void; +export type UpdateAlertsStatus = ({ alertIds, status }: UpdateAlertsStatusProps) => void; -export interface UpdateSignalStatusActionProps { +export interface UpdateAlertStatusActionProps { query?: string; - signalIds: string[]; + alertIds: string[]; status: 'open' | 'closed'; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; @@ -41,9 +41,7 @@ export interface UpdateSignalStatusActionProps { onAlertStatusUpdateFailure: (status: string, error: Error) => void; } -export type SendSignalsToTimeline = () => void; - -export interface SendSignalToTimelineActionProps { +export interface SendAlertToTimelineActionProps { apolloClient?: ApolloClient<{}>; createTimeline: CreateTimeline; ecsData: Ecs; diff --git a/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts b/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts index 303431a559e8f..651faf0b17318 100644 --- a/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/detection_engine_header_page/translations.ts @@ -17,6 +17,6 @@ export const PAGE_BADGE_TOOLTIP = i18n.translate( 'xpack.siem.detectionEngine.headerPage.pageBadgeTooltip', { defaultMessage: - 'Detections is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', + 'Alerts is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', } ); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx similarity index 72% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx index 2e6890e60fc61..2b1173f8a4843 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.test.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { NoWriteSignalsCallOut } from './index'; +import { NoWriteAlertsCallOut } from './index'; -describe('no_write_signals_callout', () => { +describe('no_write_alerts_callout', () => { it('renders correctly', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('EuiCallOut')).toBeTruthy(); }); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx similarity index 72% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx index 1950531998450..dcb50ef43a841 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/index.tsx @@ -9,13 +9,13 @@ import React, { memo, useCallback, useState } from 'react'; import * as i18n from './translations'; -const NoWriteSignalsCallOutComponent = () => { +const NoWriteAlertsCallOutComponent = () => { const [showCallOut, setShowCallOut] = useState(true); const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); return showCallOut ? ( - -

{i18n.NO_WRITE_SIGNALS_CALLOUT_MSG}

+ +

{i18n.NO_WRITE_ALERTS_CALLOUT_MSG}

{i18n.DISMISS_CALLOUT} @@ -23,4 +23,4 @@ const NoWriteSignalsCallOutComponent = () => { ) : null; }; -export const NoWriteSignalsCallOut = memo(NoWriteSignalsCallOutComponent); +export const NoWriteAlertsCallOut = memo(NoWriteAlertsCallOutComponent); diff --git a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts similarity index 52% rename from x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts rename to x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts index 065d775e1dc6a..f79ede56ef9ae 100644 --- a/x-pack/plugins/siem/public/alerts/components/no_write_signals_callout/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/no_write_alerts_callout/translations.ts @@ -6,23 +6,23 @@ import { i18n } from '@kbn/i18n'; -export const NO_WRITE_SIGNALS_CALLOUT_TITLE = i18n.translate( - 'xpack.siem.detectionEngine.noWriteSignalsCallOutTitle', +export const NO_WRITE_ALERTS_CALLOUT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.noWriteAlertsCallOutTitle', { - defaultMessage: 'Signals index permissions required', + defaultMessage: 'Alerts index permissions required', } ); -export const NO_WRITE_SIGNALS_CALLOUT_MSG = i18n.translate( - 'xpack.siem.detectionEngine.noWriteSignalsCallOutMsg', +export const NO_WRITE_ALERTS_CALLOUT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.noWriteAlertsCallOutMsg', { defaultMessage: - 'You are currently missing the required permissions to update signals. Please contact your administrator for further assistance.', + 'You are currently missing the required permissions to update alerts. Please contact your administrator for further assistance.', } ); export const DISMISS_CALLOUT = i18n.translate( - 'xpack.siem.detectionEngine.dismissNoWriteSignalButton', + 'xpack.siem.detectionEngine.dismissNoWriteAlertButton', { defaultMessage: 'Dismiss', } diff --git a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts b/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts index 407dedbf27baf..9b36d96cef9ca 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts +++ b/x-pack/plugins/siem/public/alerts/components/rules/pre_packaged_rules/translations.ts @@ -17,7 +17,7 @@ export const PRE_BUILT_MSG = i18n.translate( 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptMessage', { defaultMessage: - 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create alerts when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', } ); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx index 5823612faac13..b77de683d5f20 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/rule_actions_field/index.tsx @@ -26,7 +26,7 @@ type ThrottleSelectField = typeof SelectField; const DEFAULT_ACTION_GROUP_ID = 'default'; const DEFAULT_ACTION_MESSAGE = - 'Rule {{context.rule.name}} generated {{state.signals_count}} signals'; + 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts'; const FieldErrorsContainer = styled.div` p { diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx index 33aa02adf3f10..69e8ed10d1b34 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_about_rule/schema.tsx @@ -183,7 +183,7 @@ export const schema: FormSchema = { }), helpText: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.guideHelpText', { defaultMessage: - 'Provide helpful information for analysts that are performing a signal investigation. This guide will appear on both the rule details page and in timelines created from signals generated by this rule.', + 'Provide helpful information for analysts that are performing an alert investigation. This guide will appear on both the rule details page and in timelines created from alerts generated by this rule.', }), labelAppend: OptionalFieldLabel, }, diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx index 14afa63ef3609..0c309c8c51a15 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx @@ -168,7 +168,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateHelpText', { - defaultMessage: 'Select which timeline to use when investigating generated signals.', + defaultMessage: 'Select which timeline to use when investigating generated alerts.', } ), }, diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx index 99ff8a6727372..d010a3128b24d 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_schedule_rule/schema.tsx @@ -22,8 +22,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText', { - defaultMessage: - 'Rules run periodically and detect signals within the specified time frame.', + defaultMessage: 'Rules run periodically and detect alerts within the specified time frame.', } ), }, @@ -38,7 +37,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { - defaultMessage: 'Adds time to the look-back period to prevent missed signals.', + defaultMessage: 'Adds time to the look-back period to prevent missed alerts.', } ), }, diff --git a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts deleted file mode 100644 index b876177d5e4d1..0000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals/signals_utility_bar/translations.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const SHOWING_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle', { - values: { totalSignalsFormatted, totalSignals }, - defaultMessage: - 'Showing {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', - }); - -export const SELECTED_SIGNALS = (selectedSignalsFormatted: string, selectedSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle', { - values: { selectedSignalsFormatted, selectedSignals }, - defaultMessage: - 'Selected {selectedSignalsFormatted} {selectedSignals, plural, =1 {signal} other {signals}}', - }); - -export const SELECT_ALL_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => - i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle', { - values: { totalSignalsFormatted, totalSignals }, - defaultMessage: - 'Select all {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', - }); - -export const CLEAR_SELECTION = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle', - { - defaultMessage: 'Clear selection', - } -); - -export const BATCH_ACTIONS = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle', - { - defaultMessage: 'Batch actions', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle', - { - defaultMessage: 'View selected in hosts', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle', - { - defaultMessage: 'View selected in network', - } -); - -export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle', - { - defaultMessage: 'View selected in timeline', - } -); - -export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle', - { - defaultMessage: 'Open selected', - } -); - -export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( - 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle', - { - defaultMessage: 'Close selected', - } -); diff --git a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts b/x-pack/plugins/siem/public/alerts/components/signals/translations.ts deleted file mode 100644 index e49ff9846b9b7..0000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals/translations.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { - defaultMessage: 'Detection engine', -}); - -export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', { - defaultMessage: 'Signals', -}); - -export const SIGNALS_DOCUMENT_TYPE = i18n.translate( - 'xpack.siem.detectionEngine.signals.documentTypeTitle', - { - defaultMessage: 'Signals', - } -); - -export const OPEN_SIGNALS = i18n.translate('xpack.siem.detectionEngine.signals.openSignalsTitle', { - defaultMessage: 'Open signals', -}); - -export const CLOSED_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.closedSignalsTitle', - { - defaultMessage: 'Closed signals', - } -); - -export const LOADING_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.loadingSignalsTitle', - { - defaultMessage: 'Loading Signals', - } -); - -export const TOTAL_COUNT_OF_SIGNALS = i18n.translate( - 'xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle', - { - defaultMessage: 'signals match the search criteria', - } -); - -export const SIGNALS_HEADERS_RULE = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle', - { - defaultMessage: 'Rule', - } -); - -export const SIGNALS_HEADERS_VERSION = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle', - { - defaultMessage: 'Version', - } -); - -export const SIGNALS_HEADERS_METHOD = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle', - { - defaultMessage: 'Method', - } -); - -export const SIGNALS_HEADERS_SEVERITY = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle', - { - defaultMessage: 'Severity', - } -); - -export const SIGNALS_HEADERS_RISK_SCORE = i18n.translate( - 'xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle', - { - defaultMessage: 'Risk Score', - } -); - -export const ACTION_OPEN_SIGNAL = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.openSignalTitle', - { - defaultMessage: 'Open signal', - } -); - -export const ACTION_CLOSE_SIGNAL = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.closeSignalTitle', - { - defaultMessage: 'Close signal', - } -); - -export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate( - 'xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle', - { - defaultMessage: 'Investigate in timeline', - } -); - -export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => - i18n.translate('xpack.siem.detectionEngine.signals.closedAlertSuccessToastMessage', { - values: { totalAlerts }, - defaultMessage: - 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', - }); - -export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => - i18n.translate('xpack.siem.detectionEngine.signals.openedAlertSuccessToastMessage', { - values: { totalAlerts }, - defaultMessage: - 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', - }); - -export const CLOSED_ALERT_FAILED_TOAST = i18n.translate( - 'xpack.siem.detectionEngine.signals.closedAlertFailedToastMessage', - { - defaultMessage: 'Failed to close alert(s).', - } -); - -export const OPENED_ALERT_FAILED_TOAST = i18n.translate( - 'xpack.siem.detectionEngine.signals.openedAlertFailedToastMessage', - { - defaultMessage: 'Failed to open alert(s)', - } -); diff --git a/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx deleted file mode 100644 index b1d7f2cfe7eb5..0000000000000 --- a/x-pack/plugins/siem/public/alerts/components/signals_info/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiLoadingSpinner } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; -import React, { useState, useEffect } from 'react'; - -import { useQuerySignals } from '../../containers/detection_engine/signals/use_query'; -import { buildLastSignalsQuery } from './query.dsl'; -import { Aggs } from './types'; - -interface SignalInfo { - ruleId?: string | null; -} - -type Return = [React.ReactNode, React.ReactNode]; - -export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { - const [lastSignals, setLastSignals] = useState( - - ); - const [totalSignals, setTotalSignals] = useState( - - ); - - const { loading, data: signals } = useQuerySignals(buildLastSignalsQuery(ruleId)); - - useEffect(() => { - if (signals != null) { - const mySignals = signals; - setLastSignals( - mySignals.aggregations?.lastSeen.value != null ? ( - - ) : null - ); - setTotalSignals(<>{mySignals.hits.total.value}); - } else { - setLastSignals(null); - setTotalSignals(null); - } - }, [loading, signals]); - - return [lastSignals, totalSignals]; -}; diff --git a/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx index 81b2c4347e17c..b01edac2605eb 100644 --- a/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/user_info/index.test.tsx @@ -7,11 +7,11 @@ import { renderHook } from '@testing-library/react-hooks'; import { useUserInfo } from './index'; -import { usePrivilegeUser } from '../../containers/detection_engine/signals/use_privilege_user'; -import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; import { useKibana } from '../../../common/lib/kibana'; -jest.mock('../../containers/detection_engine/signals/use_privilege_user'); -jest.mock('../../containers/detection_engine/signals/use_signal_index'); +jest.mock('../../containers/detection_engine/alerts/use_privilege_user'); +jest.mock('../../containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../common/lib/kibana'); describe('useUserInfo', () => { diff --git a/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx b/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx index faf9016292559..049463d4066d8 100644 --- a/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/user_info/index.tsx @@ -7,8 +7,8 @@ import { noop } from 'lodash/fp'; import React, { useEffect, useReducer, Dispatch, createContext, useContext } from 'react'; -import { usePrivilegeUser } from '../../containers/detection_engine/signals/use_privilege_user'; -import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; +import { usePrivilegeUser } from '../../containers/detection_engine/alerts/use_privilege_user'; +import { useSignalIndex } from '../../containers/detection_engine/alerts/use_signal_index'; import { useKibana } from '../../../common/lib/kibana'; export interface State { diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts similarity index 56% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts index 7cb1d7d574cf8..64a55a8ec6eba 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/__mocks__/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/__mocks__/api.ts @@ -4,26 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - QuerySignals, - SignalSearchResponse, - BasicSignals, - SignalsIndex, - Privilege, -} from '../types'; -import { signalsMock, mockSignalIndex, mockUserPrivilege } from '../mock'; +import { QueryAlerts, AlertSearchResponse, BasicSignals, AlertsIndex, Privilege } from '../types'; +import { alertsMock, mockSignalIndex, mockUserPrivilege } from '../mock'; -export const fetchQuerySignals = async ({ +export const fetchQueryAlerts = async ({ query, signal, -}: QuerySignals): Promise> => - Promise.resolve(signalsMock as SignalSearchResponse); +}: QueryAlerts): Promise> => + Promise.resolve(alertsMock as AlertSearchResponse); -export const getSignalIndex = async ({ signal }: BasicSignals): Promise => +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockSignalIndex); export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockUserPrivilege); -export const createSignalIndex = async ({ signal }: BasicSignals): Promise => +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => Promise.resolve(mockSignalIndex); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts similarity index 68% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts index 67d81d19faa7c..3cd819b55685c 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.test.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.test.ts @@ -6,15 +6,15 @@ import { KibanaServices } from '../../../../common/lib/kibana'; import { - signalsMock, - mockSignalsQuery, - mockStatusSignalQuery, + alertsMock, + mockAlertsQuery, + mockStatusAlertQuery, mockSignalIndex, mockUserPrivilege, } from './mock'; import { - fetchQuerySignals, - updateSignalStatus, + fetchQueryAlerts, + updateAlertStatus, getSignalIndex, getUserPrivilege, createSignalIndex, @@ -27,41 +27,41 @@ jest.mock('../../../../common/lib/kibana'); const fetchMock = jest.fn(); mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); -describe('Detections Signals API', () => { - describe('fetchQuerySignals', () => { +describe('Detections Alerts API', () => { + describe('fetchQueryAlerts', () => { beforeEach(() => { fetchMock.mockClear(); - fetchMock.mockResolvedValue(signalsMock); + fetchMock.mockResolvedValue(alertsMock); }); test('check parameter url, body', async () => { - await fetchQuerySignals({ query: mockSignalsQuery, signal: abortCtrl.signal }); + await fetchQueryAlerts({ query: mockAlertsQuery, signal: abortCtrl.signal }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/search', { body: - '{"aggs":{"signalsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"signals":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', + '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', method: 'POST', signal: abortCtrl.signal, }); }); test('happy path', async () => { - const signalsResp = await fetchQuerySignals({ - query: mockSignalsQuery, + const signalsResp = await fetchQueryAlerts({ + query: mockAlertsQuery, signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(signalsMock); + expect(signalsResp).toEqual(alertsMock); }); }); - describe('updateSignalStatus', () => { + describe('updateAlertStatus', () => { beforeEach(() => { fetchMock.mockClear(); fetchMock.mockResolvedValue({}); }); - test('check parameter url, body when closing a signal', async () => { - await updateSignalStatus({ - query: mockStatusSignalQuery, + test('check parameter url, body when closing an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'closed', }); @@ -73,9 +73,9 @@ describe('Detections Signals API', () => { }); }); - test('check parameter url, body when opening a signal', async () => { - await updateSignalStatus({ - query: mockStatusSignalQuery, + test('check parameter url, body when opening an alert', async () => { + await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', }); @@ -88,12 +88,12 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await updateSignalStatus({ - query: mockStatusSignalQuery, + const alertsResp = await updateAlertStatus({ + query: mockStatusAlertQuery, signal: abortCtrl.signal, status: 'open', }); - expect(signalsResp).toEqual({}); + expect(alertsResp).toEqual({}); }); }); @@ -112,10 +112,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await getSignalIndex({ + const alertsResp = await getSignalIndex({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockSignalIndex); + expect(alertsResp).toEqual(mockSignalIndex); }); }); @@ -134,10 +134,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await getUserPrivilege({ + const alertsResp = await getUserPrivilege({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockUserPrivilege); + expect(alertsResp).toEqual(mockUserPrivilege); }); }); @@ -156,10 +156,10 @@ describe('Detections Signals API', () => { }); test('happy path', async () => { - const signalsResp = await createSignalIndex({ + const alertsResp = await createSignalIndex({ signal: abortCtrl.signal, }); - expect(signalsResp).toEqual(mockSignalIndex); + expect(alertsResp).toEqual(mockSignalIndex); }); }); }); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts similarity index 72% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts index 445f66457c59e..ccf35c9671836 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/api.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReindexResponse } from 'elasticsearch'; +import { UpdateDocumentByQueryResponse } from 'elasticsearch'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -15,25 +15,25 @@ import { KibanaServices } from '../../../../common/lib/kibana'; import { BasicSignals, Privilege, - QuerySignals, - SignalSearchResponse, - SignalsIndex, - UpdateSignalStatusProps, + QueryAlerts, + AlertSearchResponse, + AlertsIndex, + UpdateAlertStatusProps, } from './types'; /** - * Fetch Signals by providing a query + * Fetch Alerts by providing a query * * @param query String to match a dsl * @param signal to cancel request * * @throws An error if response is not OK */ -export const fetchQuerySignals = async ({ +export const fetchQueryAlerts = async ({ query, signal, -}: QuerySignals): Promise> => - KibanaServices.get().http.fetch>( +}: QueryAlerts): Promise> => + KibanaServices.get().http.fetch>( DETECTION_ENGINE_QUERY_SIGNALS_URL, { method: 'POST', @@ -43,19 +43,19 @@ export const fetchQuerySignals = async ({ ); /** - * Update signal status by query + * Update alert status by query * - * @param query of signals to update + * @param query of alerts to update * @param status to update to('open' / 'closed') * @param signal AbortSignal for cancelling request * * @throws An error if response is not OK */ -export const updateSignalStatus = async ({ +export const updateAlertStatus = async ({ query, status, signal, -}: UpdateSignalStatusProps): Promise => +}: UpdateAlertStatusProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', body: JSON.stringify({ status, ...query }), @@ -69,8 +69,8 @@ export const updateSignalStatus = async ({ * * @throws An error if response is not OK */ -export const getSignalIndex = async ({ signal }: BasicSignals): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { method: 'GET', signal, }); @@ -95,8 +95,8 @@ export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_INDEX_URL, { method: 'POST', signal, }); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts similarity index 98% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts index 6b0c7e0078268..cd2cc1fe390ba 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/mock.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/mock.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalSearchResponse, SignalsIndex, Privilege } from './types'; +import { AlertSearchResponse, AlertsIndex, Privilege } from './types'; -export const signalsMock: SignalSearchResponse = { +export const alertsMock: AlertSearchResponse = { took: 7, timeout: false, _shards: { @@ -902,14 +902,14 @@ export const signalsMock: SignalSearchResponse = { ], }, aggregations: { - signalsByGrouping: { + alertsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { key: '4', doc_count: 12600, - signals: { + alerts: { buckets: [ { key_as_string: '2020-01-21T04:30:00.000Z', @@ -939,9 +939,9 @@ export const signalsMock: SignalSearchResponse = { }, }; -export const mockSignalsQuery: object = { +export const mockAlertsQuery: object = { aggs: { - signalsByGrouping: { + alertsByGrouping: { terms: { field: 'signal.rule.risk_score', missing: 'All others', @@ -949,7 +949,7 @@ export const mockSignalsQuery: object = { size: 10, }, aggs: { - signals: { + alerts: { date_histogram: { field: '@timestamp', fixed_interval: '81000000ms', @@ -970,7 +970,7 @@ export const mockSignalsQuery: object = { }, }; -export const mockStatusSignalQuery: object = { +export const mockStatusAlertQuery: object = { bool: { filter: { terms: { _id: ['b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5'] }, @@ -978,7 +978,7 @@ export const mockStatusSignalQuery: object = { }, }; -export const mockSignalIndex: SignalsIndex = { +export const mockSignalIndex: AlertsIndex = { name: 'mock-signal-index', }; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts similarity index 55% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts index 2b8f54e5438df..2f3ebccdb14cd 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/translations.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/translations.ts @@ -6,29 +6,29 @@ import { i18n } from '@kbn/i18n'; -export const SIGNAL_FETCH_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', +export const ALERT_FETCH_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.alerts.errorFetchingAlertsDescription', { - defaultMessage: 'Failed to query signals', + defaultMessage: 'Failed to query alerts', } ); export const PRIVILEGE_FETCH_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorFetchingAlertsDescription', { - defaultMessage: 'Failed to query signals', + defaultMessage: 'Failed to query alerts', } ); export const SIGNAL_GET_NAME_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorGetAlertDescription', { defaultMessage: 'Failed to get signal index name', } ); export const SIGNAL_POST_FAILURE = i18n.translate( - 'xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription', + 'xpack.siem.containers.detectionEngine.alerts.errorPostAlertDescription', { defaultMessage: 'Failed to create signal index', } diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts similarity index 88% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts index 4e97c597546a7..b425cfd54a7fd 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/types.ts +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/types.ts @@ -7,17 +7,17 @@ export interface BasicSignals { signal: AbortSignal; } -export interface QuerySignals extends BasicSignals { +export interface QueryAlerts extends BasicSignals { query: object; } -export interface SignalsResponse { +export interface AlertsResponse { took: number; timeout: boolean; } -export interface SignalSearchResponse - extends SignalsResponse { +export interface AlertSearchResponse + extends AlertsResponse { _shards: { total: number; successful: number; @@ -34,13 +34,13 @@ export interface SignalSearchResponse }; } -export interface UpdateSignalStatusProps { +export interface UpdateAlertStatusProps { query: object; status: 'open' | 'closed'; signal?: AbortSignal; // TODO: implement cancelling } -export interface SignalsIndex { +export interface AlertsIndex { name: string; } diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.test.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_privilege_user.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_privilege_user.tsx diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx similarity index 61% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx index c577f291f037e..8627b953c8dac 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.test.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.test.tsx @@ -5,13 +5,13 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { useQuerySignals, ReturnQuerySignals } from './use_query'; +import { useQueryAlerts, ReturnQueryAlerts } from './use_query'; import * as api from './api'; -import { mockSignalsQuery, signalsMock } from './mock'; +import { mockAlertsQuery, alertsMock } from './mock'; jest.mock('./api'); -describe('useQuerySignals', () => { +describe('useQueryAlerts', () => { const indexName = 'mock-index-name'; beforeEach(() => { jest.resetAllMocks(); @@ -20,8 +20,8 @@ describe('useQuerySignals', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); expect(result.current).toEqual({ loading: true, @@ -34,72 +34,72 @@ describe('useQuerySignals', () => { }); }); - test('fetch signals data', async () => { + test('fetch alerts data', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); await waitForNextUpdate(); expect(result.current).toEqual({ loading: false, - data: signalsMock, - response: JSON.stringify(signalsMock, null, 2), - request: JSON.stringify({ index: [indexName] ?? [''], body: mockSignalsQuery }, null, 2), + data: alertsMock, + response: JSON.stringify(alertsMock, null, 2), + request: JSON.stringify({ index: [indexName] ?? [''], body: mockAlertsQuery }, null, 2), setQuery: result.current.setQuery, refetch: result.current.refetch, }); }); }); - test('re-fetch signals data', async () => { - const spyOnfetchQuerySignals = jest.spyOn(api, 'fetchQuerySignals'); + test('re-fetch alerts data', async () => { + const spyOnfetchQueryAlerts = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >(() => useQuerySignals(mockSignalsQuery, indexName)); + ReturnQueryAlerts + >(() => useQueryAlerts(mockAlertsQuery, indexName)); await waitForNextUpdate(); await waitForNextUpdate(); if (result.current.refetch) { result.current.refetch(); } await waitForNextUpdate(); - expect(spyOnfetchQuerySignals).toHaveBeenCalledTimes(2); + expect(spyOnfetchQueryAlerts).toHaveBeenCalledTimes(2); }); }); - test('fetch signal when index name changed', async () => { - const spyOnfetchRules = jest.spyOn(api, 'fetchQuerySignals'); + test('fetch alert when index name changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { rerender, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >((args) => useQuerySignals(args[0], args[1]), { - initialProps: [mockSignalsQuery, indexName], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], }); await waitForNextUpdate(); await waitForNextUpdate(); - rerender([mockSignalsQuery, 'new-mock-index-name']); + rerender([mockAlertsQuery, 'new-mock-index-name']); await waitForNextUpdate(); expect(spyOnfetchRules).toHaveBeenCalledTimes(2); }); }); - test('fetch signal when query object changed', async () => { - const spyOnfetchRules = jest.spyOn(api, 'fetchQuerySignals'); + test('fetch alert when query object changed', async () => { + const spyOnfetchRules = jest.spyOn(api, 'fetchQueryAlerts'); await act(async () => { const { result, waitForNextUpdate } = renderHook< [object, string], - ReturnQuerySignals - >((args) => useQuerySignals(args[0], args[1]), { - initialProps: [mockSignalsQuery, indexName], + ReturnQueryAlerts + >((args) => useQueryAlerts(args[0], args[1]), { + initialProps: [mockAlertsQuery, indexName], }); await waitForNextUpdate(); await waitForNextUpdate(); if (result.current.setQuery) { - result.current.setQuery({ ...mockSignalsQuery }); + result.current.setQuery({ ...mockAlertsQuery }); } await waitForNextUpdate(); expect(spyOnfetchRules).toHaveBeenCalledTimes(2); @@ -107,13 +107,13 @@ describe('useQuerySignals', () => { }); test('if there is an error when fetching data, we should get back the init value for every properties', async () => { - const spyOnGetUserPrivilege = jest.spyOn(api, 'fetchQuerySignals'); + const spyOnGetUserPrivilege = jest.spyOn(api, 'fetchQueryAlerts'); spyOnGetUserPrivilege.mockImplementation(() => { throw new Error('Something went wrong, let see what happen'); }); await act(async () => { - const { result, waitForNextUpdate } = renderHook>( - () => useQuerySignals(mockSignalsQuery, 'mock-index-name') + const { result, waitForNextUpdate } = renderHook>( + () => useQueryAlerts(mockAlertsQuery, 'mock-index-name') ); await waitForNextUpdate(); await waitForNextUpdate(); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx similarity index 69% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx index 531e080ed7d1f..9c992fa872705 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_query.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_query.tsx @@ -6,14 +6,14 @@ import React, { SetStateAction, useEffect, useState } from 'react'; -import { fetchQuerySignals } from './api'; -import { SignalSearchResponse } from './types'; +import { fetchQueryAlerts } from './api'; +import { AlertSearchResponse } from './types'; type Func = () => void; -export interface ReturnQuerySignals { +export interface ReturnQueryAlerts { loading: boolean; - data: SignalSearchResponse | null; + data: AlertSearchResponse | null; setQuery: React.Dispatch>; response: string; request: string; @@ -21,18 +21,18 @@ export interface ReturnQuerySignals { } /** - * Hook for using to get a Signals from the Detection Engine API + * Hook for fetching Alerts from the Detection Engine API * * @param initialQuery query dsl object * */ -export const useQuerySignals = ( +export const useQueryAlerts = ( initialQuery: object, indexName?: string | null -): ReturnQuerySignals => { +): ReturnQueryAlerts => { const [query, setQuery] = useState(initialQuery); - const [signals, setSignals] = useState< - Pick, 'data' | 'setQuery' | 'response' | 'request' | 'refetch'> + const [alerts, setAlerts] = useState< + Pick, 'data' | 'setQuery' | 'response' | 'request' | 'refetch'> >({ data: null, response: '', @@ -49,15 +49,15 @@ export const useQuerySignals = ( async function fetchData() { try { setLoading(true); - const signalResponse = await fetchQuerySignals({ + const alertResponse = await fetchQueryAlerts({ query, signal: abortCtrl.signal, }); if (isSubscribed) { - setSignals({ - data: signalResponse, - response: JSON.stringify(signalResponse, null, 2), + setAlerts({ + data: alertResponse, + response: JSON.stringify(alertResponse, null, 2), request: JSON.stringify({ index: [indexName] ?? [''], body: query }, null, 2), setQuery, refetch: fetchData, @@ -65,7 +65,7 @@ export const useQuerySignals = ( } } catch (error) { if (isSubscribed) { - setSignals({ + setAlerts({ data: null, response: '', request: '', @@ -86,5 +86,5 @@ export const useQuerySignals = ( }; }, [query, indexName]); - return { loading, ...signals }; + return { loading, ...alerts }; }; diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx similarity index 96% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx index c834e4ab14be2..d0571bfca5b2b 100644 --- a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -30,7 +30,7 @@ describe('useSignalIndex', () => { }); }); - test('fetch signals info', async () => { + test('fetch alerts info', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useSignalIndex() @@ -105,7 +105,7 @@ describe('useSignalIndex', () => { }); }); - test('if there is an error when fetching signals info, signalIndexExists === false && signalIndexName == null', async () => { + test('if there is an error when fetching alerts info, signalIndexExists === false && signalIndexName == null', async () => { const spyOnGetSignalIndex = jest.spyOn(api, 'getSignalIndex'); spyOnGetSignalIndex.mockImplementation(() => { throw new Error('Something went wrong, let see what happen'); diff --git a/x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx similarity index 100% rename from x-pack/plugins/siem/public/alerts/containers/detection_engine/signals/use_signal_index.tsx rename to x-pack/plugins/siem/public/alerts/containers/detection_engine/alerts/use_signal_index.tsx diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx index a83a85678bd03..e3eb4666522ad 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/detection_engine.tsx @@ -6,7 +6,6 @@ import { EuiButton, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useParams } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; import { connect, ConnectedProps } from 'react-redux'; @@ -15,60 +14,34 @@ import { indicesExistOrDataTemporarilyUnavailable, WithSource, } from '../../../common/containers/source'; -import { AlertsTable } from '../../../common/components/alerts_viewer/alerts_table'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; -import { - getDetectionEngineTabUrl, - getRulesUrl, -} from '../../../common/components/link_to/redirect_to_detection_engine'; +import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; -import { SiemNavigation } from '../../../common/components/navigation'; -import { NavTab } from '../../../common/components/navigation/types'; import { State } from '../../../common/store'; import { inputsSelectors } from '../../../common/store/inputs'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { InputsRange } from '../../../common/store/inputs/model'; -import { AlertsByCategory } from '../../../overview/components/alerts_by_category'; -import { useSignalInfo } from '../../components/signals_info'; -import { SignalsTable } from '../../components/signals'; +import { useAlertInfo } from '../../components/alerts_info'; +import { AlertsTable } from '../../components/alerts_table'; import { NoApiIntegrationKeyCallOut } from '../../components/no_api_integration_callout'; -import { NoWriteSignalsCallOut } from '../../components/no_write_signals_callout'; -import { SignalsHistogramPanel } from '../../components/signals_histogram_panel'; -import { signalsHistogramOptions } from '../../components/signals_histogram_panel/config'; +import { NoWriteAlertsCallOut } from '../../components/no_write_alerts_callout'; +import { AlertsHistogramPanel } from '../../components/alerts_histogram_panel'; +import { alertsHistogramOptions } from '../../components/alerts_histogram_panel/config'; import { useUserInfo } from '../../components/user_info'; import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; -import { DetectionEngineTab } from './types'; - -const detectionsTabs: Record = { - [DetectionEngineTab.signals]: { - id: DetectionEngineTab.signals, - name: i18n.SIGNAL, - href: getDetectionEngineTabUrl(DetectionEngineTab.signals), - disabled: false, - urlKey: 'detections', - }, - [DetectionEngineTab.alerts]: { - id: DetectionEngineTab.alerts, - name: i18n.ALERT, - href: getDetectionEngineTabUrl(DetectionEngineTab.alerts), - disabled: false, - urlKey: 'detections', - }, -}; export const DetectionEnginePageComponent: React.FC = ({ filters, query, setAbsoluteRangeDatePicker, }) => { - const { tabName = DetectionEngineTab.signals } = useParams(); const { loading, isSignalIndexExists, @@ -79,7 +52,7 @@ export const DetectionEnginePageComponent: React.FC = ({ hasIndexWrite, } = useUserInfo(); - const [lastSignals] = useSignalInfo({}); + const [lastAlerts] = useAlertInfo({}); const updateDateRangeCallback = useCallback( ({ x }) => { @@ -116,7 +89,7 @@ export const DetectionEnginePageComponent: React.FC = ({ return ( <> {hasEncryptionKey != null && !hasEncryptionKey && } - {hasIndexWrite != null && !hasIndexWrite && } + {hasIndexWrite != null && !hasIndexWrite && } {({ indicesExist, indexPattern }) => { return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( @@ -127,11 +100,11 @@ export const DetectionEnginePageComponent: React.FC = ({ - {i18n.LAST_SIGNAL} + {i18n.LAST_ALERT} {': '} - {lastSignals} + {lastAlerts} ) } @@ -141,7 +114,7 @@ export const DetectionEnginePageComponent: React.FC = ({ fill href={getRulesUrl()} iconType="gear" - data-test-subj="manage-signal-detection-rules" + data-test-subj="manage-alert-detection-rules" > {i18n.BUTTON_MANAGE_RULES} @@ -150,48 +123,29 @@ export const DetectionEnginePageComponent: React.FC = ({ {({ to, from, deleteQuery, setQuery }) => ( <> - - - {tabName === DetectionEngineTab.signals && ( - <> - - - - - )} - {tabName === DetectionEngineTab.alerts && ( - <> - - - - )} + <> + + + + )} diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx index 756e222c02950..1f9b1373d404d 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/index.tsx @@ -13,7 +13,6 @@ import { DetectionEnginePage } from './detection_engine'; import { EditRulePage } from './rules/edit'; import { RuleDetailsPage } from './rules/details'; import { RulesPage } from './rules'; -import { DetectionEngineTab } from './types'; const detectionEnginePath = `/:pageName(detections)`; @@ -22,11 +21,7 @@ type Props = Partial> & { url: string }; const DetectionEngineContainerComponent: React.FC = () => ( - + @@ -44,7 +39,7 @@ const DetectionEngineContainerComponent: React.FC = () => ( ( - + )} /> diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts index 1894d0ab1a9e7..d9cbcfc8979a1 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/helpers.test.ts @@ -611,7 +611,7 @@ describe('helpers', () => { const mockAction = { group: 'default', id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', - params: { message: 'ML Rule generated {{state.signals_count}} signals' }, + params: { message: 'ML Rule generated {{state.signals_count}} alerts' }, actionTypeId: '.slack', }; diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts index f35b6c8d7b00e..615882d4a7e3b 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/create/translations.ts @@ -13,7 +13,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule. export const BACK_TO_RULES = i18n.translate( 'xpack.siem.detectionEngine.createRule.backToRulesDescription', { - defaultMessage: 'Back to signal detection rules', + defaultMessage: 'Back to detection rules', } ); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx index 74110e25cc940..7197ed397717c 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/details/index.tsx @@ -42,15 +42,15 @@ import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; -import { SignalsHistogramPanel } from '../../../../components/signals_histogram_panel'; -import { SignalsTable } from '../../../../components/signals'; +import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel'; +import { AlertsTable } from '../../../../components/alerts_table'; import { useUserInfo } from '../../../../components/user_info'; import { DetectionEngineEmptyPage } from '../../detection_engine_empty_page'; -import { useSignalInfo } from '../../../../components/signals_info'; +import { useAlertInfo } from '../../../../components/alerts_info'; import { StepDefineRule } from '../../../../components/rules/step_define_rule'; import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; -import { buildSignalsRuleIdFilter } from '../../../../components/signals/default_config'; -import { NoWriteSignalsCallOut } from '../../../../components/no_write_signals_callout'; +import { buildAlertsRuleIdFilter } from '../../../../components/alerts_table/default_config'; +import { NoWriteAlertsCallOut } from '../../../../components/no_write_alerts_callout'; import * as detectionI18n from '../../translations'; import { ReadOnlyCallOut } from '../../../../components/rules/read_only_callout'; import { RuleSwitch } from '../../../../components/rules/rule_switch'; @@ -59,7 +59,7 @@ import { getStepsData, redirectToDetections, userHasNoPermissions } from '../hel import * as ruleI18n from '../translations'; import * as i18n from './translations'; import { GlobalTime } from '../../../../../common/containers/global_time'; -import { signalsHistogramOptions } from '../../../../components/signals_histogram_panel/config'; +import { alertsHistogramOptions } from '../../../../components/alerts_histogram_panel/config'; import { inputsSelectors } from '../../../../../common/store/inputs'; import { State } from '../../../../../common/store'; import { InputsRange } from '../../../../../common/store/inputs/model'; @@ -72,14 +72,14 @@ import { useMlCapabilities } from '../../../../../common/components/ml_popover/h import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; enum RuleDetailTabs { - signals = 'signals', + alerts = 'alerts', failures = 'failures', } const ruleDetailTabs = [ { - id: RuleDetailTabs.signals, - name: detectionI18n.SIGNAL, + id: RuleDetailTabs.alerts, + name: detectionI18n.ALERT, disabled: false, }, { @@ -107,7 +107,7 @@ export const RuleDetailsPageComponent: FC = ({ const [isLoading, rule] = useRule(ruleId); // This is used to re-trigger api rule status when user de/activate rule const [ruleEnabled, setRuleEnabled] = useState(null); - const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); + const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.alerts); const { aboutRuleData, modifiedAboutRuleDetailsData, defineRuleData, scheduleRuleData } = rule != null ? getStepsData({ rule, detailsView: true }) @@ -117,7 +117,7 @@ export const RuleDetailsPageComponent: FC = ({ defineRuleData: null, scheduleRuleData: null, }; - const [lastSignals] = useSignalInfo({ ruleId }); + const [lastAlerts] = useAlertInfo({ ruleId }); const mlCapabilities = useMlCapabilities(); // TODO: Refactor license check + hasMlAdminPermissions to common check @@ -166,13 +166,13 @@ export const RuleDetailsPageComponent: FC = ({ [isLoading, rule] ); - const signalDefaultFilters = useMemo( - () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), + const alertDefaultFilters = useMemo( + () => (ruleId != null ? buildAlertsRuleIdFilter(ruleId) : []), [ruleId] ); - const signalMergedFilters = useMemo(() => [...signalDefaultFilters, ...filters], [ - signalDefaultFilters, + const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ + alertDefaultFilters, filters, ]); @@ -196,7 +196,7 @@ export const RuleDetailsPageComponent: FC = ({ const ruleError = useMemo( () => rule?.status === 'failed' && - ruleDetailTab === RuleDetailTabs.signals && + ruleDetailTab === RuleDetailTabs.alerts && rule?.last_failure_at != null ? ( = ({ return ( <> - {hasIndexWrite != null && !hasIndexWrite && } + {hasIndexWrite != null && !hasIndexWrite && } {userHasNoPermissions(canUserCRUD) && } {({ indicesExist, indexPattern }) => { @@ -257,12 +257,12 @@ export const RuleDetailsPageComponent: FC = ({ border subtitle={subTitle} subtitle2={[ - ...(lastSignals != null + ...(lastAlerts != null ? [ <> - {detectionI18n.LAST_SIGNAL} + {detectionI18n.LAST_ALERT} {': '} - {lastSignals} + {lastAlerts} , ] : []), @@ -358,24 +358,24 @@ export const RuleDetailsPageComponent: FC = ({ {tabs} - {ruleDetailTab === RuleDetailTabs.signals && ( + {ruleDetailTab === RuleDetailTabs.alerts && ( <> - {ruleId != null && ( - { diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts index fc0a79fa652ff..0fe1106171054 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/translations.ts @@ -6,12 +6,9 @@ import { i18n } from '@kbn/i18n'; -export const BACK_TO_DETECTION_ENGINE = i18n.translate( - 'xpack.siem.detectionEngine.rules.backOptionsHeader', - { - defaultMessage: 'Back to detections', - } -); +export const BACK_TO_ALERTS = i18n.translate('xpack.siem.detectionEngine.rules.backOptionsHeader', { + defaultMessage: 'Back to alerts', +}); export const IMPORT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.importRuleTitle', { defaultMessage: 'Import rule…', @@ -22,7 +19,7 @@ export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.add }); export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', { - defaultMessage: 'Signal detection rules', + defaultMessage: 'Detection rules', }); export const ADD_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.addPageTitle', { diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts index 34a521ed32b12..3d2f2dc03946a 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.test.ts @@ -19,6 +19,6 @@ describe('getBreadcrumbs', () => { }, [] ) - ).toEqual([{ href: '#/link-to/detections', text: 'Detections' }]); + ).toEqual([{ href: '#/link-to/detections', text: 'Alerts' }]); }); }); diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts index 159301a07de78..e5cdbd7123ff4 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/rules/utils.ts @@ -30,13 +30,6 @@ const getTabBreadcrumb = (pathname: string, search: string[]) => { }; } - if (tabPath === 'signals') { - return { - text: i18nDetections.SIGNAL, - href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, - }; - } - if (tabPath === 'rules') { return { text: i18nRules.PAGE_TITLE, diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts index 008d660be9d88..067399f68d51a 100644 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts +++ b/x-pack/plugins/siem/public/alerts/pages/detection_engine/translations.ts @@ -7,27 +7,23 @@ import { i18n } from '@kbn/i18n'; export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.detectionsPageTitle', { - defaultMessage: 'Detections', + defaultMessage: 'Alerts', }); -export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', { - defaultMessage: 'Last signal', +export const LAST_ALERT = i18n.translate('xpack.siem.detectionEngine.lastAlertTitle', { + defaultMessage: 'Last alert', }); -export const TOTAL_SIGNAL = i18n.translate('xpack.siem.detectionEngine.totalSignalTitle', { +export const TOTAL_ALERT = i18n.translate('xpack.siem.detectionEngine.totalAlertTitle', { defaultMessage: 'Total', }); -export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', { - defaultMessage: 'Detected signals', -}); - export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', { - defaultMessage: 'External alerts', + defaultMessage: 'Detected alerts', }); export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { - defaultMessage: 'Manage signal detection rules', + defaultMessage: 'Manage detection rules', }); export const PANEL_SUBTITLE_SHOWING = i18n.translate( diff --git a/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts b/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts deleted file mode 100644 index d529d99ad3ad4..0000000000000 --- a/x-pack/plugins/siem/public/alerts/pages/detection_engine/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export enum DetectionEngineTab { - signals = 'signals', - alerts = 'alerts', -} diff --git a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts index 0037fc3cae628..4fb4e5d30ca7a 100644 --- a/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/siem/public/common/components/drag_and_drop/helpers.ts @@ -258,7 +258,7 @@ export const allowTopN = ({ 'string', ].includes(fieldType); - // TODO: remove this explicit whitelist when the ECS documentation includes signals + // TODO: remove this explicit whitelist when the ECS documentation includes alerts const isWhitelistedNonBrowserField = [ 'signal.ancestors.depth', 'signal.ancestors.id', diff --git a/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx b/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx index 8151291679e32..0294d175aef19 100644 --- a/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx +++ b/x-pack/plugins/siem/public/common/components/link_to/link_to.tsx @@ -25,7 +25,6 @@ import { RedirectToCreatePage, RedirectToConfigureCasesPage, } from './redirect_to_case'; -import { DetectionEngineTab } from '../../../alerts/pages/detection_engine/types'; import { TimelineType } from '../../../../common/types/timeline'; import { RedirectToManagementPage } from './redirect_to_management'; @@ -89,11 +88,6 @@ export const LinkToPage = React.memo(({ match }) => ( exact path={`${match.url}/:pageName(${SiemPageName.detections})`} /> - ; @@ -20,14 +18,9 @@ export type DetectionEngineComponentProps = RouteComponentProps<{ export const DETECTION_ENGINE_PAGE_NAME = 'detections'; export const RedirectToDetectionEnginePage = ({ - match: { - params: { tabName }, - }, location: { search }, }: DetectionEngineComponentProps) => { - const defaultSelectedTab = DetectionEngineTab.signals; - const selectedTab = tabName ? tabName : defaultSelectedTab; - const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`; + const to = `/${DETECTION_ENGINE_PAGE_NAME}${search}`; return ; }; @@ -66,8 +59,6 @@ const baseDetectionEngineUrl = `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; export const getDetectionEngineUrl = (search?: string) => `${baseDetectionEngineUrl}${appendSearch(search)}`; -export const getDetectionEngineAlertUrl = (search?: string) => - `${baseDetectionEngineUrl}/${DetectionEngineTab.alerts}${appendSearch(search)}`; export const getDetectionEngineTabUrl = (tabPath: string) => `${baseDetectionEngineUrl}/${tabPath}`; export const getRulesUrl = () => `${baseDetectionEngineUrl}/rules`; export const getCreateRuleUrl = () => `${baseDetectionEngineUrl}/rules/create`; diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap index 46e61f9e939ee..78b05a00cef9b 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap +++ b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/popover_description.test.tsx.snap @@ -5,7 +5,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` size="s" > ( diff --git a/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx index da0f6f59b533f..a9a0b85202fee 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/helpers.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { allEvents, defaultOptions, getOptions, rawEvents, signalEvents } from './helpers'; +import { allEvents, defaultOptions, getOptions, rawEvents, alertEvents } from './helpers'; describe('getOptions', () => { test(`it returns the default options when 'activeTimelineEventType' is undefined`, () => { @@ -19,7 +19,7 @@ describe('getOptions', () => { expect(getOptions('raw')).toEqual(rawEvents); }); - test(`it returns 'signalEvents' when 'activeTimelineEventType' is 'signal'`, () => { - expect(getOptions('signal')).toEqual(signalEvents); + test(`it returns 'alertEvents' when 'activeTimelineEventType' is 'alert'`, () => { + expect(getOptions('alert')).toEqual(alertEvents); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/helpers.ts b/x-pack/plugins/siem/public/common/components/top_n/helpers.ts index a4226cc58530a..b654eaf17b47b 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/helpers.ts +++ b/x-pack/plugins/siem/public/common/components/top_n/helpers.ts @@ -32,17 +32,17 @@ export const rawEvents: TopNOption[] = [ }, ]; -/** A (stable) array containing only the 'Signal events' option */ -export const signalEvents: TopNOption[] = [ +/** A (stable) array containing only the 'Alert events' option */ +export const alertEvents: TopNOption[] = [ { - value: 'signal', - inputDisplay: i18n.SIGNAL_EVENTS, - 'data-test-subj': 'option-signal', + value: 'alert', + inputDisplay: i18n.ALERT_EVENTS, + 'data-test-subj': 'option-alert', }, ]; /** A (stable) array containing the default Top N options */ -export const defaultOptions = [...rawEvents, ...signalEvents]; +export const defaultOptions = [...rawEvents, ...alertEvents]; /** * Returns the options to be displayed in a Top N view select. When @@ -58,8 +58,8 @@ export const getOptions = (activeTimelineEventType?: EventType): TopNOption[] => return allEvents; case 'raw': return rawEvents; - case 'signal': - return signalEvents; + case 'alert': + return alertEvents; default: return defaultOptions; } diff --git a/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx index d2b38a062091e..0505f55d507b8 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/index.test.tsx @@ -310,13 +310,13 @@ describe('StatefulTopN', () => { }); }); - test(`defaults to the 'Signals events' option when rendering in a NON-active timeline context (e.g. the Signals table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'signals'`, () => { + test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => { const filterManager = new FilterManager(mockUiSettingsForFilterManager); const wrapper = mount( { const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; - expect(props.defaultView).toEqual('signal'); + expect(props.defaultView).toEqual('alert'); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/index.tsx b/x-pack/plugins/siem/public/common/components/top_n/index.tsx index a71b27e0bd9cb..f6dc8e7c66ed0 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/index.tsx @@ -94,14 +94,14 @@ const StatefulTopNComponent: React.FC = ({ const kibana = useKibana(); // Regarding data from useTimelineTypeContext: - // * `documentType` (e.g. 'signals') may only be populated in some views, - // e.g. the `Signals` view on the `Detections` page. + // * `documentType` (e.g. 'alerts') may only be populated in some views, + // e.g. the `Alerts` view on the `Detections` page. // * `id` (`timelineId`) may only be populated when we are rendered in the // context of the active timeline. - // * `indexToAdd`, which enables the signals index to be appended to + // * `indexToAdd`, which enables the alerts index to be appended to // the `indexPattern` returned by `WithSource`, may only be populated when // this component is rendered in the context of the active timeline. This - // behavior enables the 'All events' view by appending the signals index + // behavior enables the 'All events' view by appending the alerts index // to the index pattern. const { documentType, id: timelineId, indexToAdd } = useTimelineTypeContext(); @@ -135,7 +135,7 @@ const StatefulTopNComponent: React.FC = ({ } data-test-subj="top-n" defaultView={ - documentType?.toLocaleLowerCase() === 'signals' ? 'signal' : options[0].value + documentType?.toLocaleLowerCase() === 'alerts' ? 'alert' : options[0].value } deleteQuery={timelineId === ACTIVE_TIMELINE_REDUX_ID ? undefined : deleteQuery} field={field} diff --git a/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx index 0a35b9e25754c..5e1476f62216b 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx +++ b/x-pack/plugins/siem/public/common/components/top_n/top_n.test.tsx @@ -163,11 +163,11 @@ describe('TopN', () => { }); test(`it does NOT render SignalsByCategory when defaultView is 'raw'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(false); }); }); - describe('signals view', () => { + describe('alerts view', () => { let toggleTopN: () => void; let wrapper: ReactWrapper; @@ -176,7 +176,7 @@ describe('TopN', () => { wrapper = mount( { }); test(`it renders SignalsByCategory when defaultView is 'signal'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(true); }); test(`it does NOT render EventsByDataset when defaultView is 'signal'`, () => { @@ -242,7 +242,7 @@ describe('TopN', () => { }); test(`it does NOT render SignalsByCategory when defaultView is 'all'`, () => { - expect(wrapper.find('[data-test-subj="signals-histogram-panel"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(false); }); }); }); diff --git a/x-pack/plugins/siem/public/common/components/top_n/translations.ts b/x-pack/plugins/siem/public/common/components/top_n/translations.ts index 7db55fa94d42e..a6873ab0ece7b 100644 --- a/x-pack/plugins/siem/public/common/components/top_n/translations.ts +++ b/x-pack/plugins/siem/public/common/components/top_n/translations.ts @@ -18,6 +18,6 @@ export const RAW_EVENTS = i18n.translate('xpack.siem.topN.rawEventsSelectLabel', defaultMessage: 'Raw events', }); -export const SIGNAL_EVENTS = i18n.translate('xpack.siem.topN.signalEventsSelectLabel', { - defaultMessage: 'Signal events', +export const ALERT_EVENTS = i18n.translate('xpack.siem.topN.alertEventsSelectLabel', { + defaultMessage: 'Alert events', }); diff --git a/x-pack/plugins/siem/public/common/mock/mock_ecs.ts b/x-pack/plugins/siem/public/common/mock/mock_ecs.ts index 7fbbabb29da1b..c897da69caba0 100644 --- a/x-pack/plugins/siem/public/common/mock/mock_ecs.ts +++ b/x-pack/plugins/siem/public/common/mock/mock_ecs.ts @@ -1281,7 +1281,7 @@ export const mockEcsData: Ecs[] = [ }, ]; -export const mockEcsDataWithSignal: Ecs = { +export const mockEcsDataWithAlert: Ecs = { _id: '1', timestamp: '2018-11-05T19:03:25.937Z', host: { diff --git a/x-pack/plugins/siem/public/common/mock/timeline_results.ts b/x-pack/plugins/siem/public/common/mock/timeline_results.ts index 42d14daa11a6b..4eb66acdfad65 100644 --- a/x-pack/plugins/siem/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/siem/public/common/mock/timeline_results.ts @@ -10,7 +10,7 @@ import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; import { GetAllTimeline, SortFieldTimeline, TimelineResult, Direction } from '../../graphql/types'; import { allTimelinesQuery } from '../../timelines/containers/all/index.gql_query'; -import { CreateTimelineProps } from '../../alerts/components/signals/types'; +import { CreateTimelineProps } from '../../alerts/components/alerts_table/types'; import { TimelineModel } from '../../timelines/store/timeline/model'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; export interface MockedProvidedQuery { @@ -2206,7 +2206,7 @@ export const defaultTimelineProps: CreateTimelineProps = { enabled: true, excluded: false, id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-1', + 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-1', kqlQuery: '', name: '1', queryMatch: { field: '_id', operator: ':', value: '1' }, diff --git a/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx index 9ba150a82ec9a..574260a819071 100644 --- a/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/siem/public/overview/components/alerts_by_category/index.tsx @@ -11,7 +11,6 @@ import { Position } from '@elastic/charts'; import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/alerts_viewer/translations'; -import { getDetectionEngineAlertUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; @@ -22,7 +21,7 @@ import { Query, } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../common/store'; -import { HostsType } from '../../../hosts/store/model'; +import { HostsTableType, HostsType } from '../../../hosts/store/model'; import * as i18n from '../../pages/translations'; import { @@ -32,6 +31,7 @@ import { import { MatrixHisrogramConfigs } from '../../../common/components/matrix_histogram/types'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; import { navTabs } from '../../../app/home/home_navigations'; +import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; const ID = 'alertsByCategoryOverview'; @@ -75,11 +75,14 @@ const AlertsByCategoryComponent: React.FC = ({ const kibana = useKibana(); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const urlSearch = useGetUrlSearch(navTabs.detections); + const urlSearch = useGetUrlSearch(navTabs.hosts); const alertsCountViewAlertsButton = useMemo( () => ( - + {i18n.VIEW_ALERTS} ), diff --git a/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx b/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx index d569fd61afc9c..f2ad45be93522 100644 --- a/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx +++ b/x-pack/plugins/siem/public/overview/components/signals_by_category/index.tsx @@ -6,9 +6,9 @@ import React, { useCallback } from 'react'; -import { SignalsHistogramPanel } from '../../../alerts/components/signals_histogram_panel'; -import { signalsHistogramOptions } from '../../../alerts/components/signals_histogram_panel/config'; -import { useSignalIndex } from '../../../alerts/containers/detection_engine/signals/use_signal_index'; +import { AlertsHistogramPanel } from '../../../alerts/components/alerts_histogram_panel'; +import { alertsHistogramOptions } from '../../../alerts/components/alerts_histogram_panel/config'; +import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { SetAbsoluteRangeDatePicker } from '../../../network/pages/types'; import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../../common/store'; @@ -65,10 +65,10 @@ const SignalsByCategoryComponent: React.FC = ({ ); const defaultStackByOption = - signalsHistogramOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? signalsHistogramOptions[0]; + alertsHistogramOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? alertsHistogramOptions[0]; return ( - = ({ query={query} signalIndexName={signalIndexName} setQuery={setQuery} - showTotalSignalsCount={true} - showLinkToSignals={onlyField == null ? true : false} - stackByOptions={onlyField == null ? signalsHistogramOptions : undefined} + showTotalAlertsCount={true} + showLinkToAlerts={onlyField == null ? true : false} + stackByOptions={onlyField == null ? alertsHistogramOptions : undefined} legendPosition={'right'} to={to} - title={i18n.SIGNAL_COUNT} + title={i18n.ALERT_COUNT} updateDateRange={updateDateRangeCallback} /> ); diff --git a/x-pack/plugins/siem/public/overview/pages/translations.ts b/x-pack/plugins/siem/public/overview/pages/translations.ts index b7bee15e4c5bf..7c0c00029266c 100644 --- a/x-pack/plugins/siem/public/overview/pages/translations.ts +++ b/x-pack/plugins/siem/public/overview/pages/translations.ts @@ -34,8 +34,8 @@ export const RECENT_TIMELINES = i18n.translate('xpack.siem.overview.recentTimeli defaultMessage: 'Recent timelines', }); -export const SIGNAL_COUNT = i18n.translate('xpack.siem.overview.signalCountTitle', { - defaultMessage: 'Signal count', +export const ALERT_COUNT = i18n.translate('xpack.siem.overview.alertCountTitle', { + defaultMessage: 'Alert count', }); export const TOP = (fieldName: string) => diff --git a/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx b/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx index 1136b7c8d0dc4..d018487a1ccd8 100644 --- a/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx +++ b/x-pack/plugins/siem/public/timelines/components/fields_browser/header.tsx @@ -16,8 +16,8 @@ import React, { useCallback } from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../common/containers/source'; -import { signalsHeaders } from '../../../alerts/components/signals/default_config'; -import { alertsHeaders } from '../../../common/components/alerts_viewer/default_headers'; +import { alertsHeaders } from '../../../alerts/components/alerts_table/default_config'; +import { alertsHeaders as externalAlertsHeaders } from '../../../common/components/alerts_viewer/default_headers'; import { defaultHeaders as eventsDefaultHeaders } from '../../../common/components/events_viewer/default_headers'; import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; import { OnUpdateColumns } from '../timeline/events'; @@ -104,10 +104,10 @@ const TitleRow = React.memo<{ const handleResetColumns = useCallback(() => { let resetDefaultHeaders = defaultHeaders; if (isEventViewer) { - if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'alerts') { + if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'externalAlerts') { + resetDefaultHeaders = externalAlertsHeaders; + } else if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'alerts') { resetDefaultHeaders = alertsHeaders; - } else if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'signals') { - resetDefaultHeaders = signalsHeaders; } else { resetDefaultHeaders = eventsDefaultHeaders; } diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx index 3d1a9075386c1..c52be64f94bf1 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/index.tsx @@ -9,7 +9,7 @@ import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { WithSource } from '../../../common/containers/source'; -import { useSignalIndex } from '../../../alerts/containers/detection_engine/signals/use_signal_index'; +import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; import { timelineActions, timelineSelectors } from '../../store/timeline'; import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model'; diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx index 85097f93464b3..5a3805af0ca43 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -60,7 +60,7 @@ export const eventTypeOptions: EventTypeOptionItem[] = [ inputDisplay: {i18n.RAW_EVENT}, }, { - value: 'signal', + value: 'alert', inputDisplay: {i18n.SIGNAL_EVENT}, }, ]; diff --git a/x-pack/plugins/siem/public/timelines/containers/index.tsx b/x-pack/plugins/siem/public/timelines/containers/index.tsx index 76f6bdd36ecec..5efcb84539123 100644 --- a/x-pack/plugins/siem/public/timelines/containers/index.tsx +++ b/x-pack/plugins/siem/public/timelines/containers/index.tsx @@ -27,7 +27,7 @@ import { QueryTemplate, QueryTemplateProps } from '../../common/containers/query import { EventType } from '../../timelines/store/timeline/model'; import { timelineQuery } from './index.gql_query'; import { timelineActions } from '../../timelines/store/timeline'; -import { SIGNALS_PAGE_TIMELINE_ID } from '../../alerts/components/signals'; +import { ALERTS_TABLE_TIMELINE_ID } from '../../alerts/components/alerts_table'; export interface TimelineArgs { events: TimelineItem[]; @@ -182,7 +182,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch: Dispatch) => ({ clearSignalsState: ({ id }: { id?: string }) => { - if (id != null && id === SIGNALS_PAGE_TIMELINE_ID) { + if (id != null && id === ALERTS_TABLE_TIMELINE_ID) { dispatch(timelineActions.clearEventsLoading({ id })); dispatch(timelineActions.clearEventsDeleted({ id })); } diff --git a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts index bf86d6363861e..f7e848e8a9e1b 100644 --- a/x-pack/plugins/siem/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/siem/public/timelines/store/timeline/model.ts @@ -18,7 +18,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/t export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; -export type EventType = 'all' | 'raw' | 'signal'; +export type EventType = 'all' | 'raw' | 'alert'; export type ColumnHeaderType = 'not-filtered' | 'text-filter'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index cce23d3a0d59a..9246d8cb3519b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -641,7 +641,7 @@ export const getEmptySignalsResponse = (): SignalSearchResponse => ({ _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, hits: { total: { value: 0, relation: 'eq' }, max_score: 0, hits: [] }, aggregations: { - signalsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, + alertsByGrouping: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [] }, }, }); diff --git a/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts b/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts index 413acaa2d4b0a..9fdfc9ff7be0d 100644 --- a/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts +++ b/x-pack/plugins/siem/server/lib/matrix_histogram/translations.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; export const ALL_OTHERS = i18n.translate( - 'xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel', + 'xpack.siem.detectionEngine.alerts.histogram.allOthersGroupingLabel', { defaultMessage: 'All others', } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 18186f9a1bfcd..dbb2a85eb2a22 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13853,9 +13853,6 @@ "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "Elasticから事前にパッケージ化されているルールをインストールすることができませんでした", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "Elasticから事前にパッケージ化されているルールをインストールしました", "xpack.siem.containers.detectionEngine.rules": "ルールを取得できませんでした", - "xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription": "シグナルをクエリできませんでした", - "xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription": "シグナルインデックス名を取得できませんでした", - "xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription": "シグナルインデックスを作成できませんでした", "xpack.siem.containers.detectionEngine.tagFetchFailDescription": "タグを取得できませんでした", "xpack.siem.containers.errors.dataFetchFailureTitle": "データの取得に失敗", "xpack.siem.containers.errors.networkFailureTitle": "ネットワーク障害", @@ -13972,7 +13969,6 @@ "xpack.siem.detectionEngine.detectionsPageTitle": "検出", "xpack.siem.detectionEngine.dismissButton": "閉じる", "xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton": "閉じる", - "xpack.siem.detectionEngine.dismissNoWriteSignalButton": "閉じる", "xpack.siem.detectionEngine.editRule.backToDescription": "戻る", "xpack.siem.detectionEngine.editRule.cancelTitle": "キャンセル", "xpack.siem.detectionEngine.editRule.errorMsgDescription": "申し訳ありません", @@ -13984,7 +13980,6 @@ "xpack.siem.detectionEngine.goToDocumentationButton": "ドキュメンテーションを表示", "xpack.siem.detectionEngine.headerPage.pageBadgeLabel": "ベータ", "xpack.siem.detectionEngine.headerPage.pageBadgeTooltip": "検出はまだベータ段階です。Kibana repoで問題やバグを報告して、製品の改善にご協力ください。", - "xpack.siem.detectionEngine.lastSignalTitle": "前回のシグナル", "xpack.siem.detectionEngine.mitreAttack.addTitle": "MITRE ATT&CK\\u2122脅威を追加", "xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription": "Tacticを追加...", "xpack.siem.detectionEngine.mitreAttack.tacticsDescription": "Tactic", @@ -14274,8 +14269,6 @@ "xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle": "API統合キーが必要です", "xpack.siem.detectionEngine.noIndexMsgBody": "検出エンジンを使用するには、必要なクラスターとインデックス権限のユーザーが最初にこのページにアクセスする必要があります。ヘルプについては、管理者にお問い合わせください。", "xpack.siem.detectionEngine.noIndexTitle": "検出エンジンを設定しましょう", - "xpack.siem.detectionEngine.noWriteSignalsCallOutMsg": "現在、シグナルを更新するための必要な権限がありません。サポートについては、管理者にお問い合わせください。", - "xpack.siem.detectionEngine.noWriteSignalsCallOutTitle": "シグナルインデックス権限が必要です", "xpack.siem.detectionEngine.pageTitle": "検出エンジン", "xpack.siem.detectionEngine.panelSubtitleShowing": "表示中", "xpack.siem.detectionEngine.readOnlyCallOutMsg": "現在、検出エンジンルールを作成/編集するための必要な権限がありません。サポートについては、管理者にお問い合わせください。", @@ -14380,43 +14373,6 @@ "xpack.siem.detectionEngine.ruleStatus.statusDateDescription": "ステータス日付", "xpack.siem.detectionEngine.ruleStatus.statusDescription": "前回の応答", "xpack.siem.detectionEngine.signalRuleAlert.actionGroups.default": "デフォルト", - "xpack.siem.detectionEngine.signals.actions.closeSignalTitle": "シグナルを閉じる", - "xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle": "タイムラインで調査", - "xpack.siem.detectionEngine.signals.actions.openSignalTitle": "シグナルを開く", - "xpack.siem.detectionEngine.signals.closedSignalsTitle": "閉じたシグナル", - "xpack.siem.detectionEngine.signals.documentTypeTitle": "シグナル", - "xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel": "その他すべて", - "xpack.siem.detectionEngine.signals.histogram.headerTitle": "シグナル数", - "xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle": "表示中: {modifier}{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown": "上位のデスティネーションIP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown": "上位のイベントアクション", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown": "上位のイベントカテゴリー", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown": "上位のホスト名", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown": "リスクスコア", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown": "上位のルール", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown": "上位のルールタイプ", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown": "重要度", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown": "上位のソースIP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel": "積み上げ", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown": "上位のユーザー", - "xpack.siem.detectionEngine.signals.histogram.topNLabel": "トップ{fieldName}", - "xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel": "シグナルの表示", - "xpack.siem.detectionEngine.signals.loadingSignalsTitle": "シグナルの読み込み中", - "xpack.siem.detectionEngine.signals.openSignalsTitle": "シグナルを開く", - "xpack.siem.detectionEngine.signals.tableTitle": "シグナル", - "xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle": "シグナルが検索条件に一致します", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle": "選択した項目を閉じる", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle": "選択した項目を開く", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle": "ホストで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle": "ネットワークで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle": "タイムラインで選択した項目を表示", - "xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle": "バッチ処理", - "xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle": "選択した項目をクリア", - "xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle": "すべての{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}を選択", - "xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle": "{selectedSignalsFormatted} {selectedSignals, plural, =1 {シグナル} other {シグナル}}を選択しました", - "xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle": "{totalSignalsFormatted} {totalSignals, plural, =1 {シグナル} other {シグナル}}を表示中", - "xpack.siem.detectionEngine.signalTitle": "検出したシグナル", - "xpack.siem.detectionEngine.totalSignalTitle": "合計", "xpack.siem.detectionEngine.userUnauthenticatedMsgBody": "検出エンジンを表示するための必要なアクセス権がありません。ヘルプについては、管理者にお問い合わせください。", "xpack.siem.detectionEngine.userUnauthenticatedTitle": "検出エンジンアクセス権が必要です", "xpack.siem.dragAndDrop.addToTimeline": "タイムライン調査に追加", @@ -14456,11 +14412,6 @@ "xpack.siem.eventsViewer.eventsLabel": "イベント", "xpack.siem.eventsViewer.footer.loadingEventsDataLabel": "イベントを読み込み中", "xpack.siem.eventsViewer.showingLabel": "表示中", - "xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle": "メソド", - "xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle": "リスクスコア", - "xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle": "ルール", - "xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle": "深刻度", - "xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle": "バージョン", "xpack.siem.eventsViewer.unit": "{totalCount, plural, =1 {event} other {events}}", "xpack.siem.featureCatalogue.description": "セキュリティメトリクスとログのイベントとアラートを確認します", "xpack.siem.featureCatalogue.title": "SIEM", @@ -14793,7 +14744,6 @@ "xpack.siem.overview.recentlyCreatedCasesButtonLabel": "最近作成したケース", "xpack.siem.overview.recentTimelinesSidebarTitle": "最近のタイムライン", "xpack.siem.overview.showTopTooltip": "上位の{fieldName}を表示", - "xpack.siem.overview.signalCountTitle": "シグナル数", "xpack.siem.overview.startedText": "セキュリティ情報およびイベント管理(SIEM)へようこそ。はじめに{docs}や{data}をご参照ください。今後の機能に関する情報やチュートリアルは、{siemSolution} ページをお見逃しなく。", "xpack.siem.overview.startedText.dataLinkText": "投入データ", "xpack.siem.overview.startedText.docsLinkText": "ドキュメンテーション", @@ -14974,7 +14924,6 @@ "xpack.siem.topN.allEventsSelectLabel": "すべてのイベント", "xpack.siem.topN.closeButtonLabel": "閉じる", "xpack.siem.topN.rawEventsSelectLabel": "未加工イベント", - "xpack.siem.topN.signalEventsSelectLabel": "シグナルイベント", "xpack.siem.uiSettings.defaultAnomalyScoreDescription": "

機械学習ジョブの異常がこの値を超えると SIEM アプリに表示されます。

有効な値:0 ~ 100。

", "xpack.siem.uiSettings.defaultAnomalyScoreLabel": "デフォルトの異常しきい値", "xpack.siem.uiSettings.defaultIndexDescription": "

SIEM アプリがイベントを収集する Elasticsearch インデックスのコンマ区切りのリストです。

", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 195309f640a0a..5527385c752e3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13858,9 +13858,6 @@ "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "无法安装 elastic 的预打包规则", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "已安装 elastic 的预打包规则", "xpack.siem.containers.detectionEngine.rules": "无法提取规则", - "xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription": "无法查询信号", - "xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription": "无法获取信号索引名称", - "xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription": "无法创建信号索引", "xpack.siem.containers.detectionEngine.tagFetchFailDescription": "无法提取标记", "xpack.siem.containers.errors.dataFetchFailureTitle": "数据提取失败", "xpack.siem.containers.errors.networkFailureTitle": "网络故障", @@ -13977,7 +13974,6 @@ "xpack.siem.detectionEngine.detectionsPageTitle": "检测", "xpack.siem.detectionEngine.dismissButton": "关闭", "xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton": "关闭", - "xpack.siem.detectionEngine.dismissNoWriteSignalButton": "关闭", "xpack.siem.detectionEngine.editRule.backToDescription": "返回到", "xpack.siem.detectionEngine.editRule.cancelTitle": "取消", "xpack.siem.detectionEngine.editRule.errorMsgDescription": "抱歉", @@ -13989,7 +13985,6 @@ "xpack.siem.detectionEngine.goToDocumentationButton": "查看文档", "xpack.siem.detectionEngine.headerPage.pageBadgeLabel": "公测版", "xpack.siem.detectionEngine.headerPage.pageBadgeTooltip": "“检测”仍为公测版。请通过在 Kibana 存储库中报告问题或错误,帮助我们改进产品。", - "xpack.siem.detectionEngine.lastSignalTitle": "上一信号", "xpack.siem.detectionEngine.mitreAttack.addTitle": "添加 MITRE ATT&CK\\u2122 威胁", "xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription": "选择策略......", "xpack.siem.detectionEngine.mitreAttack.tacticsDescription": "策略", @@ -14279,8 +14274,6 @@ "xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle": "需要 API 集成密钥", "xpack.siem.detectionEngine.noIndexMsgBody": "要使用检测引擎,具有所需集群和索引权限的用户必须首先访问此页面。若需要更多帮助,请联系您的管理员。", "xpack.siem.detectionEngine.noIndexTitle": "让我们来设置您的检测引擎", - "xpack.siem.detectionEngine.noWriteSignalsCallOutMsg": "您当前缺少所需的权限,无法更新信号。有关进一步帮助,请联系您的管理员。", - "xpack.siem.detectionEngine.noWriteSignalsCallOutTitle": "需要信号索引权限", "xpack.siem.detectionEngine.pageTitle": "检测引擎", "xpack.siem.detectionEngine.panelSubtitleShowing": "正在显示", "xpack.siem.detectionEngine.readOnlyCallOutMsg": "您当前缺少所需的权限,无法创建/编辑检测引擎规则。有关进一步帮助,请联系您的管理员。", @@ -14385,43 +14378,6 @@ "xpack.siem.detectionEngine.ruleStatus.statusDateDescription": "状态日期", "xpack.siem.detectionEngine.ruleStatus.statusDescription": "上次响应", "xpack.siem.detectionEngine.signalRuleAlert.actionGroups.default": "默认值", - "xpack.siem.detectionEngine.signals.actions.closeSignalTitle": "关闭信号", - "xpack.siem.detectionEngine.signals.actions.investigateInTimelineTitle": "在时间线中调查", - "xpack.siem.detectionEngine.signals.actions.openSignalTitle": "打开信号", - "xpack.siem.detectionEngine.signals.closedSignalsTitle": "已关闭信号", - "xpack.siem.detectionEngine.signals.documentTypeTitle": "信号", - "xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel": "所有其他", - "xpack.siem.detectionEngine.signals.histogram.headerTitle": "信号计数", - "xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle": "正在显示:{modifier}{totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown": "排名靠前的目标 IP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown": "排名靠前的事件操作", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown": "排名靠前的事件类别", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown": "排名靠前的主机名", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown": "风险分数", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown": "排名靠前的规则", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown": "排名靠前的规则类型", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown": "严重性", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown": "排名靠前的源 IP", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel": "堆叠依据", - "xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown": "排名靠前的用户", - "xpack.siem.detectionEngine.signals.histogram.topNLabel": "热门{fieldName}", - "xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel": "查看信号", - "xpack.siem.detectionEngine.signals.loadingSignalsTitle": "正在加载信号", - "xpack.siem.detectionEngine.signals.openSignalsTitle": "打开信号", - "xpack.siem.detectionEngine.signals.tableTitle": "信号", - "xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle": "个信号匹配搜索条件", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle": "关闭选定", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle": "打开选定", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle": "查看主机中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle": "查看网络中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle": "查看时间线中所选", - "xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle": "批量操作", - "xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle": "清除选择", - "xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle": "选择所有 {totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle": "已选择 {selectedSignalsFormatted} 个{selectedSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle": "正在显示 {totalSignalsFormatted} 个{totalSignals, plural, =1 {信号} other {信号}}", - "xpack.siem.detectionEngine.signalTitle": "检测到的信号", - "xpack.siem.detectionEngine.totalSignalTitle": "合计", "xpack.siem.detectionEngine.userUnauthenticatedMsgBody": "您没有所需的权限,无法查看检测引擎。若需要更多帮助,请联系您的管理员。", "xpack.siem.detectionEngine.userUnauthenticatedTitle": "需要检测引擎权限", "xpack.siem.dragAndDrop.addToTimeline": "添加到时间线调查", @@ -14461,11 +14417,6 @@ "xpack.siem.eventsViewer.eventsLabel": "事件", "xpack.siem.eventsViewer.footer.loadingEventsDataLabel": "正在加载事件", "xpack.siem.eventsViewer.showingLabel": "显示", - "xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle": "方法", - "xpack.siem.eventsViewer.signals.defaultHeaders.riskScoreTitle": "风险分数", - "xpack.siem.eventsViewer.signals.defaultHeaders.ruleTitle": "规则", - "xpack.siem.eventsViewer.signals.defaultHeaders.severityTitle": "严重性", - "xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle": "版本", "xpack.siem.eventsViewer.unit": "{totalCount, plural, =1 {个事件} other {个事件}}", "xpack.siem.featureCatalogue.description": "浏览安全指标和日志以了解事件和告警", "xpack.siem.featureCatalogue.title": "SIEM", @@ -14798,7 +14749,6 @@ "xpack.siem.overview.recentlyCreatedCasesButtonLabel": "最近创建的案例", "xpack.siem.overview.recentTimelinesSidebarTitle": "最近的时间线", "xpack.siem.overview.showTopTooltip": "显示热门{fieldName}", - "xpack.siem.overview.signalCountTitle": "信号计数", "xpack.siem.overview.startedText": "欢迎使用安全信息和事件管理 (SIEM)。首先,查看我们的 {docs} 或 {data}。有关即将推出的功能和教程,确保查看我们的{siemSolution}页。", "xpack.siem.overview.startedText.dataLinkText": "正在采集数据", "xpack.siem.overview.startedText.docsLinkText": "文档", @@ -14979,7 +14929,6 @@ "xpack.siem.topN.allEventsSelectLabel": "所有事件", "xpack.siem.topN.closeButtonLabel": "关闭", "xpack.siem.topN.rawEventsSelectLabel": "原始事件", - "xpack.siem.topN.signalEventsSelectLabel": "信号事件", "xpack.siem.uiSettings.defaultAnomalyScoreDescription": "

在显示异常之前要超过的默认异常分数阈值。

有效值:0 到 100。

", "xpack.siem.uiSettings.defaultAnomalyScoreLabel": "默认异常阈值", "xpack.siem.uiSettings.defaultIndexDescription": "

SIEM 应用要从其中搜索事件的 Elasticsearch 索引逗号分隔列表。

", diff --git a/x-pack/test/siem_cypress/es_archives/signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/signals/mappings.json b/x-pack/test/siem_cypress/es_archives/alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/alerts/mappings.json diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/closed_alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/closed_signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/closed_alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/closed_alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/closed_signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/closed_alerts/mappings.json diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/timeline_alerts/data.json.gz similarity index 100% rename from x-pack/test/siem_cypress/es_archives/timeline_signals/data.json.gz rename to x-pack/test/siem_cypress/es_archives/timeline_alerts/data.json.gz diff --git a/x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json b/x-pack/test/siem_cypress/es_archives/timeline_alerts/mappings.json similarity index 100% rename from x-pack/test/siem_cypress/es_archives/timeline_signals/mappings.json rename to x-pack/test/siem_cypress/es_archives/timeline_alerts/mappings.json From 36c6cd941549baecacfa8b86fd30c7d7a8edd61d Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 3 Jun 2020 06:41:18 +0200 Subject: [PATCH 06/15] [Discover] Fix missing histogram time range update when query is refreshed (#67582) --- .../public/application/angular/discover.js | 1 + test/functional/apps/discover/_discover.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index daea8b5938042..f9ccdbf5ab535 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -870,6 +870,7 @@ function discoverController( if ($scope.vis.data.aggs.aggs[1]) { $scope.bucketInterval = $scope.vis.data.aggs.aggs[1].buckets.getInterval(); } + $scope.updateTime(); } $scope.hits = resp.hits.total; diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 163022e32c82f..9f3ce667d64b5 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -233,5 +233,19 @@ export default function ({ getService, getPageObjects }) { expect(await PageObjects.discover.getNrOfFetches()).to.be(1); }); }); + + describe('empty query', function () { + it('should update the histogram timerange when the query is resubmitted', async function () { + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': '{ "from": "2015-09-18T19:37:13.000Z", "to": "now"}', + }); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.awaitKibanaChrome(); + const initialTimeString = await PageObjects.discover.getChartTimespan(); + await queryBar.submitQuery(); + const refreshedTimeString = await PageObjects.discover.getChartTimespan(); + expect(refreshedTimeString).not.to.be(initialTimeString); + }); + }); }); } From fbcb74ce285a1d35af153fe8422fcfa6bd860557 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 3 Jun 2020 09:35:44 +0300 Subject: [PATCH 07/15] Add error if filter index pattern is missing (#66979) * add error if filter index pattern is gone * docs change - why? * Fix i18n * Added a functional test for broken filters (field + index pattern) * Clarify readme * Moved readme * New warning status * Remove getAll * git pull upstream master * Fix translation files * Fix merge * added filterbar texts * disabled correction * Disable check in maps test until #64861 is resolved * Fix tests, warning state is not disabled. * Adjust warning filter - ignore filters from "foreign" index pattern, that are still applicable * Add an additional unrelaeted filter test * Update src/plugins/data/public/ui/filter_bar/_global_filter_item.scss Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Update src/plugins/data/public/ui/filter_bar/_global_filter_item.scss Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Fixed test data * Revert mapping * Update data with missing test * Update test to match data * Code review Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> Co-authored-by: Elastic Machine --- .../es_query/filters/get_display_value.ts | 4 +- .../filter_manager/compare_filters.test.ts | 16 + .../query/filter_manager/compare_filters.ts | 4 + .../ui/filter_bar/_global_filter_item.scss | 15 +- .../data/public/ui/filter_bar/filter_bar.tsx | 1 + .../ui/filter_bar/filter_editor/index.tsx | 7 +- .../data/public/ui/filter_bar/filter_item.tsx | 322 +++++++++++------- .../ui/filter_bar/filter_view/index.tsx | 12 +- .../apps/dashboard/dashboard_filter_bar.js | 32 ++ .../functional/fixtures/es_archiver/README.md | 10 + .../dashboard/current/kibana/data.json.gz | Bin 16643 -> 20510 bytes .../dashboard/current/kibana/mappings.json | 230 ++++++++++++- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../maps/embeddable/tooltip_filter_actions.js | 5 +- 15 files changed, 525 insertions(+), 135 deletions(-) create mode 100644 test/functional/fixtures/es_archiver/README.md diff --git a/src/plugins/data/common/es_query/filters/get_display_value.ts b/src/plugins/data/common/es_query/filters/get_display_value.ts index 03167f3080419..10b4dab3f46ef 100644 --- a/src/plugins/data/common/es_query/filters/get_display_value.ts +++ b/src/plugins/data/common/es_query/filters/get_display_value.ts @@ -25,6 +25,7 @@ import { Filter } from '../filters'; function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { if (!indexPattern || !key) return; + let format = get(indexPattern, ['fields', 'byName', key, 'format']); if (!format && (indexPattern.fields as any).getByName) { // TODO: Why is indexPatterns sometimes a map and sometimes an array? @@ -43,9 +44,8 @@ function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { } export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string { - const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); - if (typeof filter.meta.value === 'function') { + const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); const valueFormatter: any = getValueFormatter(indexPattern, filter.meta.key); return filter.meta.value(valueFormatter); } else { diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts index 0c3947ade8221..1e5391332e6b0 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.test.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.test.ts @@ -228,5 +228,21 @@ describe('filter manager utilities', () => { expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeFalsy(); }); + + test('should compare index with index true', () => { + const f1 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + const f2 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index', ''), + }; + + f2.meta.index = 'wassup'; + f2.meta.index = 'dog'; + + expect(compareFilters([f1], [f2], { index: true })).toBeFalsy(); + }); }); }); diff --git a/src/plugins/data/common/query/filter_manager/compare_filters.ts b/src/plugins/data/common/query/filter_manager/compare_filters.ts index 3be52a9a60977..65df6e26a25b3 100644 --- a/src/plugins/data/common/query/filter_manager/compare_filters.ts +++ b/src/plugins/data/common/query/filter_manager/compare_filters.ts @@ -21,6 +21,7 @@ import { defaults, isEqual, omit, map } from 'lodash'; import { FilterMeta, Filter } from '../../es_query'; export interface FilterCompareOptions { + index?: boolean; disabled?: boolean; negate?: boolean; state?: boolean; @@ -31,6 +32,7 @@ export interface FilterCompareOptions { * Include disabled, negate and store when comparing filters */ export const COMPARE_ALL_OPTIONS: FilterCompareOptions = { + index: true, disabled: true, negate: true, state: true, @@ -44,6 +46,7 @@ const mapFilter = ( ) => { const cleaned: FilterMeta = omit(filter, excludedAttributes); + if (comparators.index) cleaned.index = filter.meta?.index; if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); if (comparators.alias) cleaned.alias = filter.meta?.alias; @@ -81,6 +84,7 @@ export const compareFilters = ( const excludedAttributes: string[] = ['$$hashKey', 'meta']; comparators = defaults(comparatorOptions || {}, { + index: false, state: false, negate: false, disabled: false, diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss index 24adf0093af95..73ec14de82b43 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_item.scss @@ -32,15 +32,26 @@ font-style: italic; } -.globalFilterItem-isInvalid { +.globalFilterItem-isError, .globalFilterItem-isWarning { text-decoration: none; .globalFilterLabel__value { - color: $euiColorDanger; font-weight: $euiFontWeightBold; } } +.globalFilterItem-isError { + .globalFilterLabel__value { + color: makeHighContrastColor($euiColorDangerText, $euiColorLightShade); + } +} + +.globalFilterItem-isWarning { + .globalFilterLabel__value { + color: makeHighContrastColor($euiColorWarningText, $euiColorLightShade); + } +} + .globalFilterItem-isPinned { position: relative; diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index d89cf01eedd43..a54a25acc5913 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -64,6 +64,7 @@ function FilterBarUI(props: Props) { onUpdate(i, newFilter)} onRemove={() => onRemove(i)} diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx index fd228a2213795..0e2bcc7581950 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/index.tsx @@ -198,9 +198,14 @@ class FilterEditorUI extends Component { if ( this.props.indexPatterns.length <= 1 && this.props.indexPatterns.find( - (indexPattern) => indexPattern === this.state.selectedIndexPattern + (indexPattern) => indexPattern === this.getIndexPatternFromFilter() ) ) { + /** + * Don't render the index pattern selector if there's just one \ zero index patterns + * and if the index pattern the filter was LOADED with is in the indexPatterns list. + **/ + return ''; } const { selectedIndexPattern } = this.state; diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 528ec4800e7b9..c44e1faeb8e7f 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -18,9 +18,9 @@ */ import { EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { InjectedIntl } from '@kbn/i18n/react'; import classNames from 'classnames'; -import React, { Component, MouseEvent } from 'react'; +import React, { MouseEvent, useState, useEffect } from 'react'; import { IUiSettingsClient } from 'src/core/public'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; @@ -32,8 +32,9 @@ import { toggleFilterNegated, toggleFilterPinned, toggleFilterDisabled, + getIndexPatternFromFilter, } from '../../../common'; -import { getNotifications } from '../../services'; +import { getIndexPatterns } from '../../services'; interface Props { id: string; @@ -46,95 +47,123 @@ interface Props { uiSettings: IUiSettingsClient; } -interface State { - isPopoverOpen: boolean; +interface LabelOptions { + title: string; + status: string; + message?: string; } -class FilterItemUI extends Component { - public state = { - isPopoverOpen: false, - }; +const FILTER_ITEM_OK = ''; +const FILTER_ITEM_WARNING = 'warn'; +const FILTER_ITEM_ERROR = 'error'; - private handleBadgeClick = (e: MouseEvent) => { - if (e.shiftKey) { - this.onToggleDisabled(); +export function FilterItem(props: Props) { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [indexPatternExists, setIndexPatternExists] = useState(undefined); + const { id, filter, indexPatterns } = props; + + useEffect(() => { + const index = props.filter.meta.index; + if (index) { + getIndexPatterns() + .get(index) + .then((indexPattern) => { + setIndexPatternExists(!!indexPattern); + }) + .catch(() => { + setIndexPatternExists(false); + }); } else { - this.togglePopover(); + setIndexPatternExists(false); } - }; - public render() { - const { filter, id } = this.props; - const { negate, disabled } = filter.meta; - let hasError: boolean = false; + }, [props.filter.meta.index]); - let valueLabel; - try { - valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); - } catch (e) { - getNotifications().toasts.addError(e, { - title: this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorMessage', - defaultMessage: 'Failed to display filter', - }), - }); - valueLabel = this.props.intl.formatMessage({ - id: 'data.filter.filterBar.labelErrorText', - defaultMessage: 'Error', - }); - hasError = true; + function handleBadgeClick(e: MouseEvent) { + if (e.shiftKey) { + onToggleDisabled(); + } else { + setIsPopoverOpen(!isPopoverOpen); } - const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; - const dataTestSubjValue = filter.meta.value ? `filter-value-${valueLabel}` : ''; - const dataTestSubjDisabled = `filter-${ - this.props.filter.meta.disabled ? 'disabled' : 'enabled' - }`; - const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + } - const classes = classNames( + function onSubmit(f: Filter) { + setIsPopoverOpen(false); + props.onUpdate(f); + } + + function onTogglePinned() { + const f = toggleFilterPinned(filter); + props.onUpdate(f); + } + + function onToggleNegated() { + const f = toggleFilterNegated(filter); + props.onUpdate(f); + } + + function onToggleDisabled() { + const f = toggleFilterDisabled(filter); + props.onUpdate(f); + } + + function isValidLabel(labelConfig: LabelOptions) { + return labelConfig.status === FILTER_ITEM_OK; + } + + function isDisabled(labelConfig: LabelOptions) { + const { disabled } = filter.meta; + return disabled || labelConfig.status === FILTER_ITEM_ERROR; + } + + function getClasses(negate: boolean, labelConfig: LabelOptions) { + return classNames( 'globalFilterItem', { - 'globalFilterItem-isDisabled': disabled || hasError, - 'globalFilterItem-isInvalid': hasError, + 'globalFilterItem-isDisabled': isDisabled(labelConfig), + 'globalFilterItem-isError': labelConfig.status === FILTER_ITEM_ERROR, + 'globalFilterItem-isWarning': labelConfig.status === FILTER_ITEM_WARNING, 'globalFilterItem-isPinned': isFilterPinned(filter), 'globalFilterItem-isExcluded': negate, }, - this.props.className + props.className ); + } - const badge = ( - this.props.onRemove()} - onClick={this.handleBadgeClick} - data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`} - /> - ); + function getDataTestSubj(labelConfig: LabelOptions) { + const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; + const dataTestSubjValue = filter.meta.value + ? `filter-value-${isValidLabel(labelConfig) ? labelConfig.title : labelConfig.status}` + : ''; + const dataTestSubjDisabled = `filter-${isDisabled(labelConfig) ? 'disabled' : 'enabled'}`; + const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; + return `filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`; + } - const panelTree = [ + function getPanels() { + const { negate, disabled } = filter.meta; + return [ { id: 0, items: [ { name: isFilterPinned(filter) - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.unpinFilterButtonLabel', defaultMessage: 'Unpin', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.pinFilterButtonLabel', defaultMessage: 'Pin across all apps', }), icon: 'pin', onClick: () => { - this.closePopover(); - this.onTogglePinned(); + setIsPopoverOpen(false); + onTogglePinned(); }, 'data-test-subj': 'pinFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.editFilterButtonLabel', defaultMessage: 'Edit filter', }), @@ -144,47 +173,47 @@ class FilterItemUI extends Component { }, { name: negate - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.includeFilterButtonLabel', defaultMessage: 'Include results', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.excludeFilterButtonLabel', defaultMessage: 'Exclude results', }), icon: negate ? 'plusInCircle' : 'minusInCircle', onClick: () => { - this.closePopover(); - this.onToggleNegated(); + setIsPopoverOpen(false); + onToggleNegated(); }, 'data-test-subj': 'negateFilter', }, { name: disabled - ? this.props.intl.formatMessage({ + ? props.intl.formatMessage({ id: 'data.filter.filterBar.enableFilterButtonLabel', defaultMessage: 'Re-enable', }) - : this.props.intl.formatMessage({ + : props.intl.formatMessage({ id: 'data.filter.filterBar.disableFilterButtonLabel', defaultMessage: 'Temporarily disable', }), icon: `${disabled ? 'eye' : 'eyeClosed'}`, onClick: () => { - this.closePopover(); - this.onToggleDisabled(); + setIsPopoverOpen(false); + onToggleDisabled(); }, 'data-test-subj': 'disableFilter', }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.filterBar.deleteFilterButtonLabel', defaultMessage: 'Delete', }), icon: 'trash', onClick: () => { - this.closePopover(); - this.props.onRemove(); + setIsPopoverOpen(false); + props.onRemove(); }, 'data-test-subj': 'deleteFilter', }, @@ -197,63 +226,124 @@ class FilterItemUI extends Component {
{ + setIsPopoverOpen(false); + }} />
), }, ]; - - return ( - - - - ); } - private closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - }; + /** + * Checks if filter field exists in any of the index patterns provided, + * Because if so, a filter for the wrong index pattern may still be applied. + * This function makes this behavior explicit, but it needs to be revised. + */ + function isFilterApplicable() { + const ip = getIndexPatternFromFilter(filter, indexPatterns); + if (ip) return true; - private togglePopover = () => { - this.setState({ - isPopoverOpen: !this.state.isPopoverOpen, + const allFields = indexPatterns.map((indexPattern) => { + return indexPattern.fields.map((field) => field.name); }); - }; + const flatFields = allFields.reduce((acc: string[], it: string[]) => [...acc, ...it], []); + return flatFields.includes(filter.meta?.key || ''); + } - private onSubmit = (filter: Filter) => { - this.closePopover(); - this.props.onUpdate(filter); - }; + function getValueLabel(): LabelOptions { + const label = { + title: '', + message: '', + status: FILTER_ITEM_OK, + }; + if (indexPatternExists === false) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelErrorInfo', + defaultMessage: 'Index pattern {indexPattern} not found', + }, + { + indexPattern: filter.meta.index, + } + ); + } else if (isFilterApplicable()) { + try { + label.title = getDisplayValueFromFilter(filter, indexPatterns); + } catch (e) { + label.status = FILTER_ITEM_ERROR; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelErrorText', + defaultMessage: `Error`, + }); + label.message = e.message; + } + } else { + label.status = FILTER_ITEM_WARNING; + label.title = props.intl.formatMessage({ + id: 'data.filter.filterBar.labelWarningText', + defaultMessage: `Warning`, + }); + label.message = props.intl.formatMessage( + { + id: 'data.filter.filterBar.labelWarningInfo', + defaultMessage: 'Field {fieldName} does not exist in current view', + }, + { + fieldName: filter.meta.key, + } + ); + } - private onTogglePinned = () => { - const filter = toggleFilterPinned(this.props.filter); - this.props.onUpdate(filter); - }; + return label; + } - private onToggleNegated = () => { - const filter = toggleFilterNegated(this.props.filter); - this.props.onUpdate(filter); - }; + // Don't render until we know if the index pattern is valid + if (indexPatternExists === undefined) return null; + const valueLabelConfig = getValueLabel(); - private onToggleDisabled = () => { - const filter = toggleFilterDisabled(this.props.filter); - this.props.onUpdate(filter); - }; -} + // Disable errored filters and re-render + if (valueLabelConfig.status === FILTER_ITEM_ERROR && !filter.meta.disabled) { + filter.meta.disabled = true; + props.onUpdate(filter); + return null; + } -export const FilterItem = injectI18n(FilterItemUI); + const badge = ( + props.onRemove()} + onClick={handleBadgeClick} + data-test-subj={getDataTestSubj(valueLabelConfig)} + /> + ); + + return ( + { + setIsPopoverOpen(false); + }} + button={badge} + anchorPosition="downLeft" + withTitle={true} + panelPaddingSize="none" + > + + + ); +} diff --git a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx index 6ff261e3cfb8a..f9328875cc910 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx @@ -26,6 +26,7 @@ import { Filter, isFilterPinned } from '../../../../common'; interface Props { filter: Filter; valueLabel: string; + errorMessage?: string; [propName: string]: any; } @@ -34,14 +35,17 @@ export const FilterView: FC = ({ iconOnClick, onClick, valueLabel, + errorMessage, ...rest }: Props) => { const [ref, innerText] = useInnerText(); - let title = i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { - defaultMessage: 'Filter: {innerText}. Select for more filter actions.', - values: { innerText }, - }); + let title = + errorMessage || + i18n.translate('data.filter.filterBar.moreFilterActionsMessage', { + defaultMessage: 'Filter: {innerText}. Select for more filter actions.', + values: { innerText }, + }); if (isFilterPinned(filter)) { title = `${i18n.translate('data.filter.filterBar.pinnedFilterPrefix', { diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js index 417b6fb066172..6bc34a8b998a4 100644 --- a/test/functional/apps/dashboard/dashboard_filter_bar.js +++ b/test/functional/apps/dashboard/dashboard_filter_bar.js @@ -186,5 +186,37 @@ export default function ({ getService, getPageObjects }) { expect(filterCount).to.equal(1); }); }); + + describe('bad filters are loaded properly', function () { + before(async () => { + await filterBar.ensureFieldEditorModalIsClosed(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.loadSavedDashboard('dashboard with bad filters'); + }); + + it('filter with non-existent index pattern renders in error mode', async function () { + const hasBadFieldFilter = await filterBar.hasFilter('name', 'error', false); + expect(hasBadFieldFilter).to.be(true); + }); + + it('filter with non-existent field renders in error mode', async function () { + const hasBadFieldFilter = await filterBar.hasFilter('baad-field', 'error', false); + expect(hasBadFieldFilter).to.be(true); + }); + + it('filter from unrelated index pattern is still applicable if field name is found', async function () { + const hasUnrelatedIndexPatternFilterPhrase = await filterBar.hasFilter( + '@timestamp', + '123', + true + ); + expect(hasUnrelatedIndexPatternFilterPhrase).to.be(true); + }); + + it('filter from unrelated index pattern is rendred as a warning if field name is not found', async function () { + const hasWarningFieldFilter = await filterBar.hasFilter('extension', 'warn', true); + expect(hasWarningFieldFilter).to.be(true); + }); + }); }); } diff --git a/test/functional/fixtures/es_archiver/README.md b/test/functional/fixtures/es_archiver/README.md new file mode 100644 index 0000000000000..ca0f612fad06b --- /dev/null +++ b/test/functional/fixtures/es_archiver/README.md @@ -0,0 +1,10 @@ +## Changing test data sets + +If you need to update these datasets use: + + * Run the dev server `node scripts/functional_tests_server.js` + * When it starts, use `es_archiver` to load the dataset you want to change + * Make the changes you want + * Export the data by running `node scripts/es_archiver.js save data .kibana` + * Move the mapping and data files to the correct location and commit your changes + diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz index e83e34a2e07fab31fd8be22de647fdcd646dba12..a052aad9450f564426820c0354e43489a3e7e0c5 100644 GIT binary patch literal 20510 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ßgoDwAB@36~bC6353>h5$CVMgPw@{Q5YWJnSQ)Do=-_^`)3L8?$|wh6VTw zy7lrfFQ1X@sj{Ln5z`{2rbk4rKVhJX($yir{J@KUBgbyV&)^;b@Drb|0nTgjCJ@C# zR=BOp^+?1hPq^~nH(l0%iSw6cZq){24JmA-$_r5ZI?_a1N1R{5RnXxy98eSBldlY{ zi0q-3#>m|Hwba}Zt@x(v&+}{YV(~Q~M6M;Q;c``Q_?r1x#hUGi9C%V@1Ae> za~=1VO(aj;1b4&i2+Ol$U6+b)s;&&{ir(CnMW%>e$K^nl^$#?IY=NWiDyzO(JjxF8 z5)-|&ATTH}=Nvk{QvvhxO$IzLn9*T#KemLj4~t)Bb$`EdxuW)Y5U?C%JnvlSyLn9; zsN40X9~VB<28O?_P14Im>P&p!6YKEr%lJvO#oHn<_l;Fw-C1(WL{MLmWKsj$2;g|g zVS2wp#)&swyqMR%HX8pm2DufYv3X_%IxsTCd-LCxQJ$>0RC{j*ozjLr*Ix4;4gxb$ zB2OHTqs$Ws!I(L?o!@4v+H!^9BSC~&a`L|DtwF++;w<{!VLd{m%dl~Nu&SHsJQ)HpZ3QY>l~G`jEVq=N z6HnX#&c$<`V$noU<;{&qE{sZr@i`=;ksr&q^EPeHYvn;tnGEC;F&uHLZw?Ai3G-@^hh3wv8cz#0i7GcL zr!O@^X;eR4v>K}dm}Yr9Jy-%%(n>b3o3>?>*uv84aI3iX=HhnQDG>_E zvv>N~*ax7x+0_I%O(lu{>$A=yRaP7=CcWR=WMDuwjpV7I?lDMs3->$5XpfEFZyExfM=E%cW^ z96t3numu+tB188ew;8Hcc(70b%w|ySjYN*196fe{>P3&3XV4I4R->uxtk`2l0lUEp z&XL$r6BiSvzM4L&U{ajwx^1VqD3iSsJ(4C} zj~{g< z`f~3R@Xw$ERa4>&UvK9?#>ofxGsdi`4xHM8GGO7?P-u4SRX>dvT#aGuQ5jDE@E@AyEr z5|<>BVH#_ShW}Z(p>eYI${$B%b6LOglD%{NIiQF=2N`~*~&6I*2D~XWoN!Cmo4E4?$WBT zbP3#-HZSsMX5GL4>Z}pDBY_@Z9GhFcsIy)FOP`Yw){;3};%C^#WK*3_n|ni?G4Ihn z>2@Bw%Ijt>hIc-bt<+PYZRPME993378RC{$<7O%S2Pd+|$Zx=180~V@#;w-a26-{YHmuy4AuHik?ww@9weH zg|Fr5d)*PH@kko>yc@)X=y`?0m6Y3QQJk(vQ z_EGuu;`mpGbGI9neefMlL(*G?!7hC}j;4${H&bA#j@DDu)lCJ#m`xUr=a4HyQervSH(~p!3ii!R$-%pG+$1$DK5DMNg-WeO zbGH%iC01+cveX)c*`dy*3`;rfH}uMm3lGk+2{*4;>wT=&_K<0wgRjy0QhjAB_ditDsL@7xi>>{`Z==Zjo@ctuUy<%oM< zG7FTGFey+!AwbA$MJBF!1BxPMmKBXy$a2=_cO06w6Gg=WE(S)ie z@}RVBEDz!FV{bCNxC7^T=dx1>+?d_J)Y*x3RxbQqJFSh*uYyRFdTw${q~OvZT3 z#sdq1%2i_4=mouj*XR5Az)MR&?2q1b$8VdFf+HXCjE~-=;Y**S>RwTdx+q6*AJ|6k z_2QJi_oEE0!oA3?0NV7|fW({sos(buWPpzPwfw!?QMwo#RXl zpVb)5X%u)<)xFI4KIm1@%ADP7s-)Vitnzb=z*JQ?qur9!)X*T;_!P^OnPGvXSd5iu zl`%&LUwJe1(Pqj=Jz8U>d0*^$VT{O_eur8;cCtZTzMzDAF{JVWBioM=q1Ek!QY{)# z-yW^$$yv79&wi9h zYocmkX)>|PkM~DfVQ!bMc|OXuTUc^yLonz;k zC{{*Oi=o%-g%ynM9JU3y^#}=NZp#&#T%qB{SKlw-wx5djJK~YrKc;<=Zj~1Bl?7N! z?mQ;h69Rexfv+gM$(-`E_)fIjL<4*M{Leip~MHvjvTp9QW)@XifMV5 z_>qN$!T~MCJ8?^gjwh4hCV@C0mRT745eca5Q$u02h-05mW3|U{#@Wfx#u!E1ilXGp zcBEn`+z=Kn$IE=j~Z@espUr3BBNceqscDOVFfHZdN z?xbPr(ug^b+jIae2!h^W@TA?RLWVJTm$wgZ~!aFTp1jzGvJv zy>26?6>GPdeqmu2AvReIG|k-&f0gplgtb|ZWEI4jG;elLV9md8<<@4%8lukNfftY$K)L7y7_vs3!^WAwCrheq z(30OnbSGpl{UTP1mn8Zv`N{S{7c&RM`x`Cz;EaeoOj@28!HJW#qK=us*W(TQp`n1c z%=?9W$OyRWm|M;3jcbs_#}wCAQCYK*?8>oPceEjACR|>tGwoDRthI*gVhv=x!O!#@ zw1ykQxSb!^71Okh$h)nfvLmBX8G6nrcZOl+hU>&v=}5yWt;DROavM3l`K(!9`UT~4 zAUY}17mjgQbb)A7eP%R`r!I8-$J5x{BeM4L^`LiQu8VqGlGcp5BiMpwZVEJz%#5e0 zH@5?t?X<`(V#Q%Q9A{8Cc4Wg|Kl4ODebY6RXqZQCnwMndJ8;VTVfHAG_w(BB_uB2~ z_dDWkI3K`gIIyPEYM}Ky07MIs3I$ zgQDB8nEmwJVbZc_N13(faY5}+2735nB${DH8xg*$A@EA1+kwj7AIWATrV0jD16E@k zQ=U;q7Mn9lGt%$obY(u)q)hHLV|?;8W<}>pnHlL13x)~@Z&H<(fKpuXB+d(SL+t;0 zxx@{aR7lDreYa>>$(oTJl$VE*q&aG)CFG>@5k#mZ-s(|T1`q=u6kA(Dlhm1D^PzRDkAUV-ou(oJTDjYxY9hQRCK_6?$ zZv64dK@g2WY7yI=CwpLY74f7t@e)%eB99ji$Q|J?QNV93Zr&%XQ$9Jk;{4Pc_cn*q zFWJ!d%ru4j5?=ZUuU9BE9xi4o4_1!HlhVPwKk4~>#!@pl(Ivg~1!umf&J?JA>6VOz zoc7A7aaPxbWXa=$w-C64zUoAK~Zn{ClSUVmuj&lcFNT?Wy;-B6jWV>Z|I zF`aAYGFGno60O!HS&DGomCImRlOou#N+W4y+O8L@bszS1bGR(`*kc@oEU#%%Cseh( zHSFh$fTsEE<0to0F|QgdBa^Y!68PkOBhIDe^YElGj)wD!Yd7@SbTYP{*YLTGb(Vqx zvGy6RnDLF0WOz0cDpISend_8k&1nFZ^l~nYo)*$7Tf|$#>Xr0yFe`VQrjh08?QobO zX>nE1l7J=TJkVV4TdnsEydyd+jS4~~Qg2ZXTYxr2^j)Mk$ngYb^6#6l0Q|_b9C9cO z*a+Ap1{@CAj8#bI9KvYlwjl>%Fsg%y`JTX>{rnU=5 z&DmRQ7R+*P_5!TxQ((wlQ7m@HS!Xr%7B2 zFDWh$l#5LijE`Kwq}<(2 z%5m%^{0VV^DNV1^6MKp99qcI=(~}+-DPxd+SB)NCVu=OKJ4Fka!Nl@7eE|gB;Us3=VB)^!m%sCV? zMV);4GDKEG?oR!~>mjD|EB}ZXF}OgN11q`R3$|dsA$AhV7EwaM4~tX!Ao~4 ziEPa%jH~QHvr1&iCnv;m`T}oILdF4Ii z>Mpz%S6+fPAfbIyzf&AG=ta&n{ZS=Ss@0zx?2}=YJ1wt1Z%sEbi{e^uXO2xoU$mN; z->rYjY`7~MgI`>LLIwJqirdb&+^qz$4s=B=+wQ9TL({Nef>R?MkonsVdg&$FXU?C0 zlWI2zz!BxSb4d=R=%?@i#eEB!)i_6G3;Oj@))+jDA&J5sI3mqxFDu7@h2=OjEshqdAamI#}QA-hqQ+dVXsGVrm z@!!v{qX^?$EO6jc>c8PmsT$IzS0Mb-wF37(*!lu$9 zjDQW~Iu4q{Vg#}X0a}okWR+>$dC{gL!Pkx+^W>i!s~HMH5tHXUNio%9;eZ zoPLmNXkTp;)P#szm4j!lme}}&agSwIQ{mB%_UdffPG42qcPdQS=BOq zCc*11Ae9nnr*0*6JeMpzeLLfJ*Vq7FgUnKjGI_fT)!F|%(FPtYi9DD~f|+C8K z)aJU?XUC%GbizX1xWKdSFV6|uTf;JGbxkDLI3XzC5Y4qZC88h1<_Psg3U1|u)S|aM zC6yMfti&!$D75hnth77oaLo<8#QF&4EaMm7yr8fi5w|*}B*&VA`<%LXZ`5dg6wnr(SR3q_8LU9)hv^};b=620CE zSplzM#a-DzOR4c4Fehqic|^uBRb0)%K~K7d8!%Pg;DDfIsStCEAl?B?smTraoA7tT z1B`5ov(V-hhSg|khROVIw=+O=s|Q$SXTti`w`=@W$5Zp8U}m4Fs7ng&%EA-!H7{=- z<#zg>=C;Wsde1@zKAuBfV+d84`jHOy+66CF*fNUxv@5p=aEn#>3%!xM%B@$a?Rrr} z5jHE23KX>%V!0jAtP$>~Y;hmuj4+$X_sR69Vw>uU`S!wu@5;BNm9z%4xZ%8W!{YNg zJQXJMFk`uQyA(}KcUmh=OI`kay3)Z`F*SjnpNKN(Pqk@!H?p1WG|dmZx1BT^FHxZc z?fjQj50~%r(3?;xdfVk+Q9(bUK!=w*1P$-@K~C+`k~TFZ9VXvyN;~MiI@0r!sb0+_ zK<_pb{*JPOHXf2I;tF4ObHa)nU&XKX-#OCJTLcRG<(Oofr}cd{Mc=TD=5wje_(tY3 zS`1xQ|9KB3*)STLAPV>#YjyIYT*f!hSLzXPoUz?f4;HvZq=_5QgG7|(B zo1lf>66Z|kaA^qHSMy+x1&SQHN(2vWiCnzg$JC>LBhF0ffthJLD}P?L#GjpAM~Oe= zvoWILc@f|cj5mI)?p)Br$54IkGdV;8P4YY#bW-2~3weKPHU{7iUoq7c2s|Bnd)|4^ zAK*CmE;M!m?se&Iu07`nK_++T&YSqdbb%$7iYyT^+!mb*kU8fVK^L=^A4y-F&6I72 zdUp04?3!uUUtxI=-?v(!iLju>mV^mHT7@tOmA)kv%lkKFwx#*(=_V+I9DmXh;OdI` z3`8M+L$tYODVI!k<;mdTpa^`jiuPTeJ^GtosLgMORm0XZw60O>frF34nQw-!qenA^ zbVKjJk&0U|YwkF1zXPUQB%4^d6VbaoA7H>_AXP2RsO66i&F|H zde`jCd1r0Ok~3<7Dt*_y(w}ans1Pdv??28sv+4>4<%37b_Pi*Sx}Un7m%Iub>Dy%% z2W3}{T08Gxf4b!4GN_hyG#vr(0dV8GSiATz0jqD695BIkND2K{9k5DQc}ewSbUc+2 zUW<$|$G`<=!}hw_`abx}H*MH@fR2F+$UXn$kdXtu^PjETuvGw=j8bfvAn!_i{=^+S zAd9<_VuF`@IZL7`q&rKBf(q*tDL3ZZ^2A)}#{KTyImbxmC@SGDNlh+Y&qdCC77M|^ z_Brb}t4=)mhH2UQ0{n4Um1$rpbO5$Rkl8n2y!3M@Co&y6QF4H*Z<*!j3uHE0e0m~S zhVO1rW!BwBJN^&*IM6N8o8Z#&81QgGW|e!b-I++NjnYZ9lX~La?s#!qBi$^ewX>VP znTN4qBo*?>#{z~O@k!1BoUe~*UQL1RVc;iIR&VWX6F;DHeXWcXTnI1!Yi(!k0l&q( z85l8EdZ5;TtHc^hH?GRoyKDG+llk2yfl}rYYm@9T9y0xvaT#RycA~MxK=m^7K_C$i zAED3IP~+-CWtTodm179t;q4I(T;^NpiV_+|#E~)=alt14qJHgpZ`-PN zfmd6fnObCYT>(ZNvJp8vuK*V~(`l*YO-)&3BxN1S!8rIGufVgKRWCF#y&Mg{6Wc!mm3@($+b`aLI_m;=)@nmD&ohxD&od}SbGN?8VXpLOE9Tw0vQIr> z&q}Z~!$XeBJ^dAkCNKh^+BerX)xYLjoGSP7MuYp2ZaMOJb!%Tpa+XzZ?B2R0;T_kfI#lEW_>Tb9lA4%oxmRxNfIA zz{5I|b_5-Lb#*l;zH}0UTL`O+Y0sF`zUUn^PgKVqb}5e3C!OR#O|Q2Y4_WGgFTC!@ zU+?=T&{yUsX=zi=7#s$*?pCl1{b7|?p6r~AUgue7{tkZ#_yGwoUhM#o_46}oc=h*- zOXXd$SkE{@o=?nSFTt$bt=|uI<3{s_0r&99dmsvS6$Y{0#ZN9Cul&RVYp30x8L$^q zwCz^Jf)AL9-xKc4FoC)-d2tw$@N@vDveagiGMsZN()m*F_%p2nzg{Y0#RQ8)Z{A{R zjc?YDwjf4o++GhA5J;g)9V@FaUl9p%QGOi@2wqoDj95P%6?9RX%v7!CY10ZcBT?M$ z2QM_N#e!fLRKrd{{wp*b-95(51L2LNj5Rl`1nzU9zOL$P4cMf519-C)Mk!RdoW9ns zNnSiBRl5veIS<+>RRIl?wCGCT*Rt zaF(WeOgui6<51Wfrp-f}y0}H!2#YiyubpMKOx8V`2mSEKVoF!|j~x!8ck&GKeep%k z(4Atf?S*y>4rF5L!B1i$j;;kdUe=sRysr1#^UJ}Y*PELY7r>n617qSdl|j-<6Pxs( zl1|m3Ed|~Cg;Q84D3I;OUhR1z#Ta2|G*XJbc%a50@?72a`2*2%S!Dvgfk(A34syiW z#4U0ZA7KYar0-;ET)Ir91j&a=IcnbXa`CL9;9C>Rg1j=ctWq>VO<7b7ig8m~o0x}A9;`%2 zF(t0`x-V@QFe0SF1b0l2<7lzF_1|EP{$uUY59Ncne8<5DX~E_%7`>$FzaCFQD5hF; zd#(>;8J5wW)<~zMgfYS}A`0#6R^gMzo^$X46BM^aFk1hrL=C9NCoVn&$vrm>yk@3j zy=2}!oh7?KEWhcUGEi)pNcWbKtsRyDA&-QsJ~=zhio;O%DTU-jgU+d@F7!&48A*#I z#mxR>3#)qIGgAKe9bH;_dnp2+z;(DYLD`&H7NfOA>6$Xk6J)qj%K>Qjg?w*;mh0To zOP=MGA!yE4eH5DVN>KRyJWeD!jI}_O=KL%kl{T-`Z{y>k93GcaVo?=~R6AC%lu5c~ z7;5PIAKp$wRGLPLt*KWQV{L+^UHC=-5;-T!rLu(2U4q)>D{Urj zpIJr{)hI8ioEP-_)V!xjf%RkjY@08d%AZ&rAF}fS~4PG8>~cp4g)mB;P(% zeOKI1imFQsHQk_4HW{Vz(YQV_U;6%6B#Ni5ctb3)NtJtUIka{i)6By2$_<__YhPy9 zj%C&&oohzBh7uK5tCKa9GL1sP&p|gLA-|D+%mW+d6-QXX?tDM980QS>&XLZnlGphk zN*)yk&(h}G^-QpCc4liYivzo$cbmqA2cw%u zrPpmL4p8^Uvo4a4UmH{EO)`>1b?xJEGtBQsHA&&4CUjrpxHJ|`uvShMPl9Rvmu5gf z-(MHenleR_T84L%CQWs5GJp+;Y1JHwiSxS9`6*{>s3?s*bmqgNAO1f`qD7Dy>0G+&og8ZS9BaZEiGAmTeCt4UXtN4ARL4mI><`t$P|50MRErnUxs| ze|jB=2@E_gVEGp{DFEtSnkHng-f)dXGD>*$hj(-y%%%^hcE_o|t9TOf#61t_Miw^S z85-2>ZV^~bQ}1GTc`$ZIdneSQ|cS zEiGtUwjzejV|Mn674H;NcI3}3Fg!gMgg+KAFJg?-aI3Vqe$2;oa}g!hM)j(^bNd+h zm;E&UsBx91S4~Vl2yBS3B6%QA+81v8GGme!4t}mRC*tRCDo;?{66Fkv! zIBAbe=F`s9Ru=H6==KVWg>OcH7dU6qU+?%*i3s?Yid01O3@Hz=v{D^cI_WP zcjw)HyaRXr&g4vqP+5U!e*3!5p6hPg#mhoxk#pe>>B88G2 zj!%)T(jLdxnGj3k_Ea^Jd#~n=U}f3HsHiN^XjM?J%aa_j3j0%7EvMG{1_YJhokn+K zGHw4Sao@4QV#WQ*J_Y%y2Na9I`81NUXL*ho7dkg^s`=iMA zLa*Qac58oAw~B=qAtcRTaoC;3)(QwOSM*+nB0)s(m)b9ptgc#u?5gH!??@me1y)t7 zNypaf+4a$dw#S!PXBR$YxELnLx|Z$$3qKN^ezLMyFLh-`vO%mzegt)6M4K4#;)?G;LaP)^K^QJQ?HK%50*WD~jrV zm@FI~;F9#VvFB?c?Q4cmx7t!jxW(yT5Iu3$V;ZNKTm}r8me5X@xeCtX#oO(Z3!oh2 z>7`pf{RYV|rzgWOLd}01M}2gKB{{&Bo4Cexxb#U`6Xvth(b2a|anQ0~`{!vei*0`x z{SN=@I|I~~*(@FP4gD=#`79F#;yrgbdP`X9U?CpB;@cuTjUl2|$kwgyOAoRX3@M*8 zlza?{EF*Ye(iUJl=y60GjmKXVDBbzK$CEfRB9D~#{xr&e7zADDJ?RIUJ192(w7?tj zGq1lKG2J9_oDsq?2Ulk7Il6QJ_zoHA&SsYfsMe6Y?_dDx8o577B5tZ4Ncf$oCkMxC zl&;~9&rUHu&_Dffh=m|u4}c8-&pbpHp%0G!hr_^lBoOp|JDf~S_x(z9F7ad?(m5=+b0nM20$cs2K!7yHd99*ElzjVqd*OJDA}gy+f-; zF~*p4!;t`K_szLwBt25@Lwxv1e}g~T?izt0kSlVkC%0+5@DQX9t1alcZcv%r~52_`y;c`oy|(0rK2@fn(E)56p0Q z?NkaFTk#~7{=_kaaqMhIeC@i2 z-7428ua1KV2s5}ifzh^W?PF|xwTxgA5ODbTtJDR6>Z}r}P9PlULXweB5CgDGv3zRK z-7t|DAdt&26$0{~{vdrSXP_p6k%D-{fHy)}Q|yA`E3GkY{=oQXr5%tvr=H*A2>FkZQ&FTOt02;srYw7HUZrP#XvEAypGcX-m$? zRl>yj`M>7UqR~qZcWLlFV9(L#=OLw@?0S>#zDXy)P`8m(`vJ}lxb5~opH?-#2ulaB z%SDu{>~Q;z+X;Y|Y@xxin0=~6Cj{iLoys|e?z#HMEvLt`ECZKH=^sI(nVNtI6QLip z7ew_gQc=P<=FxsZSD4W5z4D&1hib;bnx1pN;qD|J>td6CJWh!{hbzvo*uNCI2khb} zNzH+z7{re{Nx<%HB|oR?Yneysad8(yVr4=@IG%AL0;-y!=p*XEJp5#jbx|Zk{RoiN zc!u50(3+S%n`tvNNMZCa1L5-O`OGv3CI!$Xeu*UJ2!$4b`duD$EI{>Y9R{cP@;^AT z@VB2re1TT37(s`cQcpQkArmGtkTTvO+t$j*@@Lm4P(%&4m$-+~@@MV_QRSK@qdV&X z5?$hp-(!}ln5-(w+Gg$j6A}!{K0O0CID?q z`->r^`(xn6>%qKWCCP=nV$T8hJIl8MErgDO{0a+zEXqFi__0L#}CcWP3wU_M0(`5J?#v-EQ$zZDn*96(kXH) zgfN({;vR)D0vGBiUCIQ$=tIB4{i~B~IE8HJ#a~3{%P#^x(T}=Ai>MTSD+mx3)~2XI z?=S$pmQ7Zkw%*S+mqXT_<>Y(09)t?7;LJf<2@OQzhU654oYd04rXFU??&!6jXA`ov zdJw4fN|wO%b`nZ!gDDsU{T}g;!NO5H3K}whX3*3p;};y? zT~U0?uO-;}#*Cb)E>FXD0F<5_b^x6I_jtUw^*>*BCGmg%mlu$$D+MuQKZr-C$H#|A zPfiyEo}kxUcYb1hFpN-- zq9VtGR@(ErHF+92nUf>-F>{o{MtpJyz1jHqIBL@Ja5P{YF^%5)o#0haCnnDEn_9cT zaovtgg8t)KxmGo{^UB{Yi+KT%`AQRYHee}59r9*I1>m>$xGC?lWbJlLu8VLbE8eI? z2C$)--9VlnmnkHBX;3)p)UN(j^zhQjxnX%8WlLS_SwxhAH5qbaHy9s-Q@2zQ=CCAE<$s92#n4k;z}x|KJpEQe1^$)IGUiTqH6vJ_T1H>u30Mqlif7w(H&ItpXU zkDn~<;6${s_4YY^kRNUMqTV>xcj8v5bet=>?p;$FhJIc9x9{U>R{6N!bD0g;H{a{;8@680@LY*;yv+#6S$^fLf{ z6vwN)CFc&Fo~}<@a^-Az^S-=zTiy?GyKFgv@+HieGsf5WTb^A{X}T1K?*o7jAdO?m0m!f@PMrZ7$XAP5PhAFA9 z_c>k5?$qtw7FTSLOZ-Jjc*Ri4>>7dGCOzJ?390@H&yMEk1fJflTpJ9ca~!eklJu&8 zx%408fz&KjldsF6kiIg%DLLy@Y=Fz@d;73?Wb(oWqO14UJa~&`nzY zEZH)yC@m(nlte?-?(o}fEIsa&Oolb1sH@@hV!QH!U8dGjG&WC_Xl$J@RVtV?bCi&u z2~dTaS)p`eZ8B6sohcNzY?wXKuq}fcblu!kuWfGoYk6TQqZ}(e$uzK;2ezYr(XIs6 zr5S4S`aU{wVY$dyImzbBN5{8lx~|<^o^~%Zuh}ma_3T zvd9Q(2nPx<42j*OT+9306*th8WlU1I`R=B{W5J7|e%Ng*RM{o(X)-hY0%`Ll?QT2@ z5z`e}C1=fjKb|a`!Q=4UW6Lzy$}F@fdZnA)SbM~MC9mKUrPQir6}w>o zw?&CX`_-%so94mgHC0wOF!QIQWcoi@9KylWtV42r=Aq1qcHZy*j^a?0{!bLA5lEjg zz47Uh?5L`4Y&orM9o)X?m*qV^1ro&f=h&e|{`asASiu&C*ZWFjadgiwVxT|5{qAUT z%LbV_AK;Ias#9GR#LYRSE|c;B9ZtN45J&zlImC1$VMq58_U*Is%%LWLC2w2rga8#h zya8c?-=N8Pw?CK~DKfOeP2HDeqVB|^-HN+=PGen6Lg=-r%d>QtobCLeGPRJ~8}r)Y zu|sI4(W}htleSBFW~D{mRy!u}@{Q!sKHSJP@#4`#1tG}O$oR?FbL{NJb@dKs)lR6; zG6N~)>-Kf++sz2SQnHtdo!GnzWZ*-j+gmm~s0xi-%Qfr#mFk3Sdn}|XurC=E#^a7` z^~XwpgKDsjN-&1<*U)qRF&tSWSYz3SsHk4c+6-kl2G*M*R#B`qq^hZvrTV3xxy4W-7voMi z7E6Q2Fujg2s{Tn94X=_$-YRdRpsI35e{iGSK}Wk~n>9(^da5|p3x%;)%g^q#ib=Z^ zy$X=(We-G^uA#!`cS6Ak@JhhL8g&V~5(tbslLqQe8zmLYMw;v(6Dr9q)_Z9&mNuu& z{tJ*Xyk)t_UN-pF9|yGx#p;9_t4-#O7fO{*5mZ)Y&AV5HI8poau@)4#kag6yK^$|< zv`ISFwa<%&{t~4!xihEgkb+A87mi+cV=W@3dFc{_y@&C?T*SZ0ayK-n@Ooa>41|)t zQ>>_|_Z(xGf4vt626>Cs`s2s#*J?09A|HH26>qqM`VKdO?V2z8kZ;KM2#hk!+#WK9 zZTE1!MjS4nWJxk=*-eHy?bnmKl{C7-Zi=pXz&+gIwi0If+fXd225yx&i*=v>iHvQt zlUoBM$xJ1`-!DrMB{Tu|_d9@!-b<~{-lbizN2apdl7@Sshf}p;I!DJ2$}Y0rHX893 zk2em5w`_9ncH#XrOypcjjrJvj+i4C1_v=U$JpV1zjH>a?PjaSk_**OvPHf8D1)kM% zs`obB^|Wn$u7G_(csc%;wG=wp(UC|6r%r^_q`LMABBNVfhbB9H3r6ZL3d!Uck$stI zj2C95ON&s7*bw<+E7fC`cdr!+45{{;fUW^yMWkKT> z!E9_O+gfG(8e-Ap>{!j6AuRmj{?)YuPVt*>s=BC5RY5oG1y)Kt5TsAe7bR7?zZ#D4 zGK(l4Aa0Eo5*PoRyquxhZH6MFZ(4)({&^=3ETPW7mW-n$hHlxxaB3>rlZc`U*O3a% zV3{s?ptty;3e=>@-8_UTNCLawvyF=#u#~7dqEaEVQv>vwrtrT3TqJo}Et$)eCNIgk zyquw+o58*+RcOnl*w-re?IkT6^81}499H``rmCb>ldg8h)whbBA0f;PRjiMLmiLy( zW<|q3?NhlhR&k*vX5Ay_3Rb$SO*pG5-L^_6A7+@KYGAH)+W4RE*?lGHhp(0?jZ832w^4u9$a0JDj7a7e&xQ_5r7jcC zJ7;L+a4B>S@q+e|AoZ2fNOGAPn=vE`-0{Ye z2-%V_|4*rZ7N)023hVLCi@r@zi7jI3*L4cPl=k8Tr@%J;=vfmpxf9=4tv$>#BD_fV zKj+d?aog0r2Aug29dF$eZyZVX2=h;yFF$>{lA zz(IeNkcj`)vfH6II@D=ezJY`H;zOaf=dGy-!LeB(U}CSR-WeKO-Wl6ZcXy9XQGh|qESu5HBf77NyYA%>+~VZ&a0+dA=wZBJv;Y%x_nCXvO+2*L6Yj)rvu>quNIqw#eBNNv@GJBsOI7SY166 z#$rXWp?nv_%sWYnjOMb^v&qd~GupF(< zFLAc_EMlYwY5B@z!*q}|=g?MA^*>fWoCs!sl#CR_flv%%ZA>)kDJ~BC-?g+W@gZU{ z^68>f!ClzM%JgatLfb6w!Xfo$Kp{B9Z?}KRX{=r|e@y7qH^M#C&oq z?Zt=T;Ia2tdvfdB1PmmZ;j)rtn#j40GgrB-b`EoyWVdJ{bAI%*EQBVMFNGHEzh#EfJj)n##(*y{=ddenCaBefuTguNdrBrPjYdrAPC!_;35f8^_;P z3bO@o#Mm<_M@SO;Xr+UAR;09G8lA57M~MlBRJoVV$H}Pt4$Lb{0X}steK-1fwfa3S zf=0pH|KyNwStFl)|Ip7B{LyrlJSE6O0K`J{JLX-w>G%Od|JB8jWh5WO#qa+Kbw{VK zj_Br~D`3PJ{~URl2mCkG?F)ZfM4&T5(ivotIZyee>Ib#qKEpBdFph{!kau5)1=v~Y zH6(sS%})V%;=QdBD@=d05Jd9pf$)dGDB6?zp!-S|*qs%6Cw$=x>L<;w9S3<*^xctW zCRk<4bQBNIi}$x1Izz1YF93+j>jnBpbtfd^{6RxPCGqcJ@z%I}BcOx3B?O=bfI?fz z`X6@uv$9DIuzjKahv@+&1_&3L21@ z;Fq}iBi+~wIKB;B$-X0XrZ0|m$jG-1o)@#LeF4l*j_{rVJ&WOeP$gE9aHKt0W@UaD z^gUJPOCBLDu2~=82c=Fx`0X~B`jB7SzQP?VG5iw6oKXVvIevWaGTDq;&K@COB$;Fj zJdkjBSdkt{=va*0UP}C5Y@DOy1n=0N8+I6qhV{lv8pdu#Z=emDu0D|aDPr4RbfUOS zESrQIG6=g-cwTmad|=u70bl!s!%l^3cpH@-B-klz4S@7KG$yK@%r%8UJkaEIsNFO` zMSmS6oA1AoE zq%B2AV?5}Oc)cQ{6CrgARnVmh*eGA0mmE4YM#;f;HU2xSImVnEqT>O`99;#2(NE`nJ#iW*z}{DZQHCW?YkH~E{@c4$ zZpMy)LcUpV7V-nY|4*vhZ=w1O zx1XakF>^87Zb-T8^nU`~6p8-%z+RVoZHtiI8ixT%zCRu!2QY5z0loaop4RMpyH2SezEEhhGQRAF7%B*0VsL<53;T_$49M@ zH)&6}LLoqVfix(%2OGN7CK{dKKOoZVYH|ic??xpxniQY^o8`*ZGrMIwJ_iu^|KBW^ z4;c>IbhbZE1qO0O(&i_)$L^;%K^n+X8I@kKa17)<8@3eKcmD7+gB!62gp-ml$TFhu z=k5Oub1NLB`_jo<&@1Os>Lz-Dhw9MIf~nE7*BGSG_KFyjEp3s%=U`Lu+<2uH0O|t3 z)043V-aRqHtCKdRQB`foM`=yZ8E3GOcVhE#WWz%$DW5Z&B%7K~*?h+X$emWWT%Gi+R_gTy57LXamPWs2H$VMDx!!2?N~<3$wXj7xtM7I} z0;N2=4|#7#stAuK z4dpWwj_8pHcv%M&6i8esV+?7M_8=B8sgd}l3rPnPPJcW>q`doorM%<6jK;UI7<~5d zV9L$Sv17im;JfYrIs`ffd^d_ z13`v>Dx!m>uv6R_@c2wO+Yw=)*+Tq+14vB5P}s#U@El=!=Kp`7T!#xvuJ+QmaKfe?tf}C-xPD<;W<=OgnjX~6MyAf*Y5m_p&eM>w)gd<{O4Fj(4!P&U7 z!s&O|p28vGAgLxw$e*T>rg=(b!wRUg$@*%cGQ(|B+Q0IUvI=7aLzfM%U|b^%`%S)N zK9+XDE)gqVwaXYiE{*|QhCITLZ&egv>r8Tg5I^zNg=sKB; zq9=M%9-(Y>%$gR97Af;l$bJ$hj|Z*n+@7L;KfW^msS7`nhy0Ap|HpuN$VI&x_U*s1 z4*jvLCkSc8(FNrDpQ6xl{fULyy!g5vLN&b|Q~bV|pH=US++L9A_sPRbyZg6m{$TRG z!BJgi9~e&^YF5&N{vreZ6q)c3h!hi-KOpUL3v zCtg=%SdDuunNf{mJ<@l7b}GmpNk1rBdiv}hMiMX`qDI=@BZkwhYl{+3%_Fv@%I9d_ z-gSSDmTTTuJp03JOxqJ1=iVQ)kFC6lC~d>`da`xx@b1Y$Cl;W_Fx@fF*4h&TGKvU~ z>kGzJ`lf9Bk2h6Xy`0*oi@5>*t6|k73}|&Zjx4sPzyIBVu_EWKF7J-TI7 zY?e!(DsvqZNCw6w#!gjsPsVGX!=a$(XKq>Ot)}K)DJ(`(-1Kf5u-X*%y0CjB@l_5- zVFSjfy(Q2U)sng67E~t}L~A5b@|$_2&neU~k}lc#I*vXiBx%HJXoAHz!bV0>ZHOFE z7-f=f^{cK`s&FP~+U6sS8dA23m8CQ4=Lzc*yc!%!3Ov!>Cxljh|8<2aZa>S1o+_qh zQ|GKt$l+QY<65a&ly>iR^wefu_EwFmG-ossKijq<1l*qTi?xAL>8Him813 zl7V`-ao&K;H#|uNPRcrYJ{o_4HAQBrfY0aynHr+8C zi%$Qb$E|$CsDM}H+?vP28s%4w{2bfUY%A+L&fV|nOzb;CshnbJFIBcVir&f!Y^?w$ z8KDf-grve_?{nBQo2fXVDdbq-=zwFhR-(_ktyk(^I)ssOsj#V88|)%n@hRtQX*cv$ zuVY}G-06>lkIXUXOf}YL)DX?i|BNkZz|dH0799$KY3OTepm1JTPHyguHjt`RPuk#B zFX-bN@(e38n-(mymxH*u@*L~*_<6rBqNx&{hD9Z+9geJHGA{Ew^V~&CmtpcCW~e%F z$OaeExE;_>x_n-kmp{X!D?Psz90sJp%Vv-~P diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json b/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json index 7f4cdd2906d44..9f5edaad0fe76 100644 --- a/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json +++ b/test/functional/fixtures/es_archiver/dashboard/current/kibana/mappings.json @@ -1,13 +1,74 @@ { "type": "index", "value": { - "index": ".kibana", + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "url": "b675c3be8d76ecf029294d51dc7ec65d", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, "dynamic": "strict", "properties": { + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" + } + } + }, "config": { "dynamic": "true", "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, "buildNum": { "type": "keyword" }, @@ -40,6 +101,9 @@ }, "notifications:lifetime:warning": { "type": "long" + }, + "xPackMonitoring:showBanner": { + "type": "boolean" } } }, @@ -92,9 +156,6 @@ "title": { "type": "text" }, - "uiStateJSON": { - "type": "text" - }, "version": { "type": "integer" } @@ -122,6 +183,122 @@ }, "title": { "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" } } }, @@ -161,6 +338,34 @@ } } }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, "timelion-sheet": { "properties": { "description": { @@ -202,9 +407,23 @@ } } }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, "type": { "type": "keyword" }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, "updated_at": { "type": "date" }, @@ -222,7 +441,6 @@ "url": { "fields": { "keyword": { - "ignore_above": 2048, "type": "keyword" } }, @@ -242,7 +460,7 @@ } } }, - "savedSearchId": { + "savedSearchRefName": { "type": "keyword" }, "title": { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index dbb2a85eb2a22..bbb622f452967 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -565,7 +565,6 @@ "data.filter.filterBar.filterItemBadgeIconAriaLabel": "削除", "data.filter.filterBar.includeFilterButtonLabel": "結果を含める", "data.filter.filterBar.indexPatternSelectPlaceholder": "インデックスパターンの選択", - "data.filter.filterBar.labelErrorMessage": "フィルターを表示できませんでした", "data.filter.filterBar.labelErrorText": "エラー", "data.filter.filterBar.moreFilterActionsMessage": "フィルター:{innerText}。他のフィルターアクションを使用するには選択してください。", "data.filter.filterBar.negatedFilterPrefix": "NOT ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5527385c752e3..07169a2b73da8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -565,7 +565,6 @@ "data.filter.filterBar.filterItemBadgeIconAriaLabel": "删除", "data.filter.filterBar.includeFilterButtonLabel": "包括结果", "data.filter.filterBar.indexPatternSelectPlaceholder": "选择索引模式", - "data.filter.filterBar.labelErrorMessage": "无法显示筛选", "data.filter.filterBar.labelErrorText": "错误", "data.filter.filterBar.moreFilterActionsMessage": "筛选:{innerText}。选择以获取更多筛选操作。", "data.filter.filterBar.negatedFilterPrefix": "非 ", diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js index 9b5b82efe6f51..a996910d4787a 100644 --- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js @@ -32,8 +32,9 @@ export default function ({ getPageObjects, getService }) { await testSubjects.click('mapTooltipCreateFilterButton'); await testSubjects.click('applyFiltersPopoverButton'); - const hasSourceFilter = await filterBar.hasFilter('name', 'charlie'); - expect(hasSourceFilter).to.be(true); + // TODO: Fix me #64861 + // const hasSourceFilter = await filterBar.hasFilter('name', 'charlie'); + // expect(hasSourceFilter).to.be(true); const hasJoinFilter = await filterBar.hasFilter('shape_name', 'charlie'); expect(hasJoinFilter).to.be(true); From 4a1b05c843c39beb3ea2d4db35f444e4b120c428 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 11:35:55 +0200 Subject: [PATCH 08/15] Lens editor auto refresh (#65868) --- .../public/react_expression_renderer.test.tsx | 25 ++++++++++++++ .../public/react_expression_renderer.tsx | 16 ++++++++- .../editor_frame/editor_frame.tsx | 1 + .../editor_frame/suggestion_panel.test.tsx | 2 ++ .../editor_frame/suggestion_panel.tsx | 19 +++++++++-- .../editor_frame/workspace_panel.test.tsx | 34 ++++++++++++------- .../editor_frame/workspace_panel.tsx | 9 ++++- 7 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index 702f88d785756..7c1711f056d69 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -88,6 +88,31 @@ describe('ExpressionRenderer', () => { expect(instance.find(EuiProgress)).toHaveLength(0); }); + it('updates the expression loader when refresh subject emits', () => { + const refreshSubject = new Subject(); + const loaderUpdate = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: loaderUpdate, + destroy: jest.fn(), + }; + }); + + const instance = mount(); + + act(() => { + refreshSubject.next(); + }); + + expect(loaderUpdate).toHaveBeenCalled(); + + instance.unmount(); + }); + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index a83c63443906b..bf716a3b9b1e8 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -19,7 +19,7 @@ import React, { useRef, useEffect, useState, useLayoutEffect } from 'react'; import classNames from 'classnames'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; @@ -38,6 +38,10 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; + /** + * An observable which can be used to re-run the expression without destroying the component + */ + reload$?: Observable; } export type ReactExpressionRendererType = React.ComponentType; @@ -63,6 +67,7 @@ export const ReactExpressionRenderer = ({ renderError, expression, onEvent, + reload$, ...expressionLoaderOptions }: ReactExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); @@ -135,6 +140,15 @@ export const ReactExpressionRenderer = ({ }; }, [hasCustomRenderErrorHandler, onEvent]); + useEffect(() => { + const subscription = reload$?.subscribe(() => { + if (expressionLoaderRef.current) { + expressionLoaderRef.current.update(expression, expressionLoaderOptions); + } + }); + return () => subscription?.unsubscribe(); + }, [reload$, expression, ...Object.values(expressionLoaderOptions)]); + // Re-fetch data automatically when the inputs change useShallowCompareEffect( () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 90405b98afe65..07c76a81ed62d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -305,6 +305,7 @@ export function EditorFrame(props: EditorFrameProps) { dispatch={dispatch} ExpressionRenderer={props.ExpressionRenderer} stagedPreview={state.stagedPreview} + plugins={props.plugins} /> ) } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index 6b0f0338d4015..fd509c0046e13 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -21,6 +21,7 @@ import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel'; import { getSuggestions, Suggestion } from './suggestion_helpers'; import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import chartTableSVG from '../../..assets/chart_datatable.svg'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; jest.mock('./suggestion_helpers'); @@ -85,6 +86,7 @@ describe('suggestion_panel', () => { dispatch: dispatchMock, ExpressionRenderer: expressionRendererMock, frame: createMockFramePublicAPI(), + plugins: { data: dataPluginMock.createStartContract() }, }; }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 0f0885d696ba4..b06b316ec79aa 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -24,10 +24,14 @@ import classNames from 'classnames'; import { Action, PreviewState } from './state_management'; import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; -import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; +import { + ReactExpressionRendererProps, + ReactExpressionRendererType, +} from '../../../../../../src/plugins/expressions/public'; import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers'; import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry'; +import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; const MAX_SUGGESTIONS_DISPLAYED = 5; @@ -52,6 +56,7 @@ export interface SuggestionPanelProps { ExpressionRenderer: ReactExpressionRendererType; frame: FramePublicAPI; stagedPreview?: PreviewState; + plugins: { data: DataPublicPluginStart }; } const PreviewRenderer = ({ @@ -154,6 +159,7 @@ export function SuggestionPanel({ frame, ExpressionRenderer: ExpressionRendererComponent, stagedPreview, + plugins, }: SuggestionPanelProps) { const currentDatasourceStates = stagedPreview ? stagedPreview.datasourceStates : datasourceStates; const currentVisualizationState = stagedPreview @@ -204,6 +210,13 @@ export function SuggestionPanel({ visualizationMap, ]); + const AutoRefreshExpressionRenderer = useMemo(() => { + const autoRefreshFetch$ = plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(); + return (props: ReactExpressionRendererProps) => ( + + ); + }, [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$, ExpressionRendererComponent]); + const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState(-1); useEffect(() => { @@ -296,7 +309,7 @@ export function SuggestionPanel({ defaultMessage: 'Current', }), }} - ExpressionRenderer={ExpressionRendererComponent} + ExpressionRenderer={AutoRefreshExpressionRenderer} onSelect={rollbackToCurrentVisualization} selected={lastSelectedSuggestion === -1} showTitleAsLabel @@ -312,7 +325,7 @@ export function SuggestionPanel({ icon: suggestion.previewIcon, title: suggestion.title, }} - ExpressionRenderer={ExpressionRendererComponent} + ExpressionRenderer={AutoRefreshExpressionRenderer} key={index} onSelect={() => { trackUiEvent('suggestion_clicked'); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx index 59b5f358e190f..49d12e9f41440 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx @@ -21,11 +21,17 @@ import { ReactWrapper } from 'enzyme'; import { DragDrop, ChildDragDropProvider } from '../../drag_drop'; import { Ast } from '@kbn/interpreter/common'; import { coreMock } from 'src/core/public/mocks'; -import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { + DataPublicPluginStart, + esFilters, + IFieldType, + IIndexPattern, +} from '../../../../../../src/plugins/data/public'; import { TriggerId, UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks'; import { TriggerContract } from '../../../../../../src/plugins/ui_actions/public/triggers'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; describe('workspace_panel', () => { let mockVisualization: jest.Mocked; @@ -34,6 +40,7 @@ describe('workspace_panel', () => { let expressionRendererMock: jest.Mock; let uiActionsMock: jest.Mocked; + let dataMock: jest.Mocked; let trigger: jest.Mocked>; let instance: ReactWrapper; @@ -41,6 +48,7 @@ describe('workspace_panel', () => { beforeEach(() => { trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked>; uiActionsMock = uiActionsPluginMock.createStartContract(); + dataMock = dataPluginMock.createStartContract(); uiActionsMock.getTrigger.mockReturnValue(trigger); mockVisualization = createMockVisualization(); mockVisualization2 = createMockVisualization(); @@ -69,7 +77,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -92,7 +100,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -115,7 +123,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -152,7 +160,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -240,7 +248,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -292,7 +300,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -372,7 +380,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -427,7 +435,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -482,7 +490,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); @@ -520,7 +528,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -564,7 +572,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); }); @@ -620,7 +628,7 @@ describe('workspace_panel', () => { dispatch={mockDispatch} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} - plugins={{ uiActions: uiActionsMock }} + plugins={{ uiActions: uiActionsMock, data: dataMock }} /> ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx index 44dd9f8364870..76da38ead6523 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx @@ -36,6 +36,7 @@ import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; +import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; export interface WorkspacePanelProps { activeVisualizationId: string | null; @@ -54,7 +55,7 @@ export interface WorkspacePanelProps { dispatch: (action: Action) => void; ExpressionRenderer: ReactExpressionRendererType; core: CoreStart | CoreSetup; - plugins: { uiActions?: UiActionsStart }; + plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart }; } export const WorkspacePanel = debouncedComponent(InnerWorkspacePanel); @@ -135,6 +136,11 @@ export function InnerWorkspacePanel({ framePublicAPI.filters, ]); + const autoRefreshFetch$ = useMemo( + () => plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(), + [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$] + ); + useEffect(() => { // reset expression error if component attempts to run it again if (expression && localState.expressionBuildError) { @@ -224,6 +230,7 @@ export function InnerWorkspacePanel({ className="lnsExpressionRenderer__component" padding="m" expression={expression!} + reload$={autoRefreshFetch$} onEvent={(event: ExpressionRendererEvent) => { if (!plugins.uiActions) { // ui actions not available, not handling event... From 0a23bfb08cb695f93a62854806bea604bfe4e321 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 13:29:54 +0200 Subject: [PATCH 09/15] TSVB: handle division by zero in math agg (#67111) --- .../response_processors/series/math.js | 36 +++-- .../response_processors/series/math.test.js | 148 ++++++++++++++++++ 2 files changed, 170 insertions(+), 14 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js index 6c8fe7ca16619..f8752ce8fa3a8 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js @@ -91,21 +91,29 @@ export function mathAgg(resp, panel, series, meta) { // a safety check for the user const someNull = values(params).some((v) => v == null); if (someNull) return [ts, null]; - // calculate the result based on the user's script and return the value - const result = evaluate(mathMetric.script, { - params: { - ...params, - _index: index, - _timestamp: ts, - _all: all, - _interval: split.meta.bucketSize * 1000, - }, - }); - // if the result is an object (usually when the user is working with maps and functions) flatten the results and return the last value. - if (typeof result === 'object') { - return [ts, last(flatten(result.valueOf()))]; + try { + // calculate the result based on the user's script and return the value + const result = evaluate(mathMetric.script, { + params: { + ...params, + _index: index, + _timestamp: ts, + _all: all, + _interval: split.meta.bucketSize * 1000, + }, + }); + // if the result is an object (usually when the user is working with maps and functions) flatten the results and return the last value. + if (typeof result === 'object') { + return [ts, last(flatten(result.valueOf()))]; + } + return [ts, result]; + } catch (e) { + if (e.message === 'Cannot divide by 0') { + // Drop division by zero errors and treat as null value + return [ts, null]; + } + throw e; } - return [ts, result]; }); return { id: split.id, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js new file mode 100644 index 0000000000000..79cfd2ddd54bb --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.test.js @@ -0,0 +1,148 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mathAgg } from './math'; +import { stdMetric } from './std_metric'; + +describe('math(resp, panel, series)', () => { + let panel; + let series; + let resp; + beforeEach(() => { + panel = { + time_field: 'timestamp', + }; + series = { + chart_type: 'line', + stacked: false, + line_width: 1, + point_size: 1, + fill: 0, + id: 'test', + label: 'Math', + split_mode: 'terms', + split_color_mode: 'gradient', + color: '#F00', + metrics: [ + { + id: 'avgcpu', + type: 'avg', + field: 'cpu', + }, + { + id: 'mincpu', + type: 'min', + field: 'cpu', + }, + { + id: 'mathagg', + type: 'math', + script: 'divide(params.a, params.b)', + variables: [ + { name: 'a', field: 'avgcpu' }, + { name: 'b', field: 'mincpu' }, + ], + }, + ], + }; + resp = { + aggregations: { + test: { + meta: { + bucketSize: 5, + }, + buckets: [ + { + key: 'example-01', + timeseries: { + buckets: [ + { + key: 1, + avgcpu: { value: 0.25 }, + mincpu: { value: 0.125 }, + }, + { + key: 2, + avgcpu: { value: 0.25 }, + mincpu: { value: 0.25 }, + }, + ], + }, + }, + ], + }, + }, + }; + }); + + test('calls next when finished', () => { + const next = jest.fn(); + mathAgg(resp, panel, series)(next)([]); + expect(next.mock.calls.length).toEqual(1); + }); + + test('creates a series', () => { + const next = mathAgg(resp, panel, series)((results) => results); + const results = stdMetric(resp, panel, series)(next)([]); + expect(results).toHaveLength(1); + + expect(results[0]).toEqual({ + id: 'test:example-01', + label: 'example-01', + color: 'rgb(255, 0, 0)', + stack: false, + seriesId: 'test', + lines: { show: true, fill: 0, lineWidth: 1, steps: false }, + points: { show: true, radius: 1, lineWidth: 1 }, + bars: { fill: 0, lineWidth: 1, show: false }, + data: [ + [1, 2], + [2, 1], + ], + }); + }); + + test('turns division by zero into null values', () => { + resp.aggregations.test.buckets[0].timeseries.buckets[0].mincpu = 0; + const next = mathAgg(resp, panel, series)((results) => results); + const results = stdMetric(resp, panel, series)(next)([]); + expect(results).toHaveLength(1); + + expect(results[0]).toEqual( + expect.objectContaining({ + data: [ + [1, null], + [2, 1], + ], + }) + ); + }); + + test('throws on actual tinymath expression errors', () => { + series.metrics[2].script = 'notExistingFn(params.a)'; + expect(() => + stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([]) + ).toThrow(); + + series.metrics[2].script = 'divide(params.a, params.b'; + expect(() => + stdMetric(resp, panel, series)(mathAgg(resp, panel, series)((results) => results))([]) + ).toThrow(); + }); +}); From f8759d4571101d4410373b2fb01b9226f8ae66db Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 13:34:14 +0200 Subject: [PATCH 10/15] Deprecate kibana.defaultAppId setting (#67635) --- docs/setup/docker.asciidoc | 2 +- docs/setup/settings.asciidoc | 4 +++- src/plugins/kibana_legacy/server/index.ts | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 12ee96b21b0c7..e8029ed1bbe9b 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -93,7 +93,7 @@ Some example translations are shown here: [horizontal] **Environment Variable**:: **Kibana Setting** `SERVER_NAME`:: `server.name` -`KIBANA_DEFAULTAPPID`:: `kibana.defaultAppId` +`SERVER_BASEPATH`:: `server.basePath` `MONITORING_ENABLED`:: `monitoring.enabled` In general, any setting listed in <> can be diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 42d616c80119b..1be9d5b1ef35b 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -216,7 +216,9 @@ on the {kib} index at startup. {kib} users still need to authenticate with | Enables use of interpreter in Visualize. *Default: `true`* | `kibana.defaultAppId:` - | The default application to load. *Default: `"home"`* + | *deprecated* This setting is deprecated and will get removed in Kibana 8.0. +Please use the `defaultRoute` advanced setting instead. +The default application to load. *Default: `"home"`* | `kibana.index:` | {kib} uses an index in {es} to store saved searches, visualizations, and diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts index 98c754795e947..0188f9b1ec515 100644 --- a/src/plugins/kibana_legacy/server/index.ts +++ b/src/plugins/kibana_legacy/server/index.ts @@ -17,7 +17,13 @@ * under the License. */ -import { CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server'; +import { + ConfigDeprecationLogger, + CoreSetup, + CoreStart, + PluginConfigDescriptor, +} from 'kibana/server'; +import { get } from 'lodash'; import { configSchema, ConfigSchema } from '../config'; @@ -29,6 +35,18 @@ export const config: PluginConfigDescriptor = { deprecations: ({ renameFromRoot }) => [ // TODO: Remove deprecation once defaultAppId is deleted renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', true), + (completeConfig: Record, rootPath: string, log: ConfigDeprecationLogger) => { + if ( + get(completeConfig, 'kibana.defaultAppId') === undefined && + get(completeConfig, 'kibana_legacy.defaultAppId') === undefined + ) { + return completeConfig; + } + log( + `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead` + ); + return completeConfig; + }, ], }; From 884704d847ef170928bd59c725a2ef95a2ee1200 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Jun 2020 15:15:30 +0200 Subject: [PATCH 11/15] relax color rule validation (#67759) --- .../vis_type_timeseries/server/routes/post_vis_schema.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts b/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts index c74d1dd72d761..e8838f57ae365 100644 --- a/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts +++ b/src/plugins/vis_type_timeseries/server/routes/post_vis_schema.ts @@ -66,6 +66,7 @@ const backgroundColorRulesItems = schema.object({ id: stringOptionalNullable, background_color: stringOptionalNullable, color: stringOptionalNullable, + operator: stringOptionalNullable, }); const gaugeColorRulesItems = schema.object({ @@ -73,7 +74,7 @@ const gaugeColorRulesItems = schema.object({ text: stringOptionalNullable, id: stringOptionalNullable, operator: stringOptionalNullable, - value: schema.number(), + value: schema.maybe(schema.nullable(schema.number())), }); const metricsItems = schema.object({ field: stringOptionalNullable, From 6f3e1bfbe5bdddf9a8c404bf83e4bee3f0b2c683 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 3 Jun 2020 09:48:54 -0400 Subject: [PATCH 12/15] [Uptime] Create new atomic params type for status alerts (#67720) * Create new atomic params type for status alerts. * Update executor params typing to support both alert params types. * Update snapshot for alert factory function. * Fix broken types and refresh snapshots. * Clean up naming of action/selector. * Fix a bug and add tests. Co-authored-by: Elastic Machine --- .../stringify_kueries.test.ts.snap | 0 .../combine_filters_and_user_search.test.ts | 0 .../lib}/__tests__/stringify_kueries.test.ts | 28 +++ .../lib}/combine_filters_and_user_search.ts | 0 x-pack/plugins/uptime/common/lib/index.ts | 8 + .../lib}/stringify_kueries.ts | 4 + .../runtime_types/alerts/status_check.ts | 27 ++- .../overview/alerts/alert_monitor_status.tsx | 6 +- .../alert_monitor_status.tsx | 10 +- .../filters_expression_select.tsx | 22 +- .../time_expression_select.tsx | 3 +- .../overview/kuery_bar/kuery_bar.tsx | 11 +- x-pack/plugins/uptime/public/hooks/index.ts | 1 + .../public/hooks/update_kuery_string.ts | 2 +- .../uptime/public/hooks/use_search_text.ts | 22 ++ .../__tests__/monitor_status.test.ts | 96 ++------ .../public/lib/alert_types/monitor_status.tsx | 41 +--- .../plugins/uptime/public/lib/helper/index.ts | 2 - .../plugins/uptime/public/state/actions/ui.ts | 2 + .../__tests__/__snapshots__/ui.test.ts.snap | 2 + .../state/reducers/__tests__/ui.test.ts | 4 + .../uptime/public/state/reducers/ui.ts | 8 + .../state/selectors/__tests__/index.test.ts | 1 + .../uptime/public/state/selectors/index.ts | 2 + .../lib/adapters/framework/adapter_types.ts | 2 +- .../lib/alerts/__tests__/status_check.test.ts | 210 +++++++++++++++++- .../uptime/server/lib/alerts/status_check.ts | 110 +++++++-- .../server/lib/requests/get_index_pattern.ts | 2 +- .../server/lib/requests/uptime_requests.ts | 3 +- 29 files changed, 474 insertions(+), 155 deletions(-) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/__tests__/__snapshots__/stringify_kueries.test.ts.snap (100%) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/__tests__/combine_filters_and_user_search.test.ts (100%) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/__tests__/stringify_kueries.test.ts (69%) rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/combine_filters_and_user_search.ts (100%) create mode 100644 x-pack/plugins/uptime/common/lib/index.ts rename x-pack/plugins/uptime/{public/lib/helper => common/lib}/stringify_kueries.ts (93%) create mode 100644 x-pack/plugins/uptime/public/hooks/use_search_text.ts diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_kueries.test.ts.snap b/x-pack/plugins/uptime/common/lib/__tests__/__snapshots__/stringify_kueries.test.ts.snap similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_kueries.test.ts.snap rename to x-pack/plugins/uptime/common/lib/__tests__/__snapshots__/stringify_kueries.test.ts.snap diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/combine_filters_and_user_search.test.ts b/x-pack/plugins/uptime/common/lib/__tests__/combine_filters_and_user_search.test.ts similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/combine_filters_and_user_search.test.ts rename to x-pack/plugins/uptime/common/lib/__tests__/combine_filters_and_user_search.test.ts diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts b/x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts similarity index 69% rename from x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts rename to x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts index db1a846c295a0..4c32b19765427 100644 --- a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_kueries.test.ts +++ b/x-pack/plugins/uptime/common/lib/__tests__/stringify_kueries.test.ts @@ -59,4 +59,32 @@ describe('stringifyKueries', () => { kueries.set('monitor.id', ['https://elastic.co', 'https://example.com']); expect(stringifyKueries(kueries)).toMatchSnapshot(); }); + + it('handles precending empty array', () => { + kueries = new Map( + Object.entries({ + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + tags: [], + 'url.port': [], + }) + ); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"(observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + ); + }); + + it('handles skipped empty arrays', () => { + kueries = new Map( + Object.entries({ + tags: [], + 'monitor.type': ['http'], + 'url.port': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + }) + ); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"monitor.type:http and (observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + ); + }); }); diff --git a/x-pack/plugins/uptime/public/lib/helper/combine_filters_and_user_search.ts b/x-pack/plugins/uptime/common/lib/combine_filters_and_user_search.ts similarity index 100% rename from x-pack/plugins/uptime/public/lib/helper/combine_filters_and_user_search.ts rename to x-pack/plugins/uptime/common/lib/combine_filters_and_user_search.ts diff --git a/x-pack/plugins/uptime/common/lib/index.ts b/x-pack/plugins/uptime/common/lib/index.ts new file mode 100644 index 0000000000000..2daec0adf87e4 --- /dev/null +++ b/x-pack/plugins/uptime/common/lib/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './combine_filters_and_user_search'; +export * from './stringify_kueries'; diff --git a/x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts b/x-pack/plugins/uptime/common/lib/stringify_kueries.ts similarity index 93% rename from x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts rename to x-pack/plugins/uptime/common/lib/stringify_kueries.ts index d34ee9665de33..490fd1661f782 100644 --- a/x-pack/plugins/uptime/public/lib/helper/stringify_kueries.ts +++ b/x-pack/plugins/uptime/common/lib/stringify_kueries.ts @@ -35,6 +35,10 @@ export const stringifyKueries = (kueries: Map>): .reduce((prev, cur, index, array) => { if (array.length === 1 || index === 0) { return cur; + } else if (cur === '') { + return prev; + } else if (prev === '' && !!cur) { + return cur; } return `${prev} and ${cur}`; }, ''); diff --git a/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts b/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts index 909669bb5d3eb..74d5337256601 100644 --- a/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts +++ b/x-pack/plugins/uptime/common/runtime_types/alerts/status_check.ts @@ -6,7 +6,30 @@ import * as t from 'io-ts'; -export const StatusCheckExecutorParamsType = t.intersection([ +export const StatusCheckFiltersType = t.type({ + 'monitor.type': t.array(t.string), + 'observer.geo.name': t.array(t.string), + tags: t.array(t.string), + 'url.port': t.array(t.string), +}); + +export type StatusCheckFilters = t.TypeOf; + +export const AtomicStatusCheckParamsType = t.intersection([ + t.type({ + numTimes: t.number, + timerangeCount: t.number, + timerangeUnit: t.string, + }), + t.partial({ + search: t.string, + filters: StatusCheckFiltersType, + }), +]); + +export type AtomicStatusCheckParams = t.TypeOf; + +export const StatusCheckParamsType = t.intersection([ t.partial({ filters: t.string, }), @@ -20,4 +43,4 @@ export const StatusCheckExecutorParamsType = t.intersection([ }), ]); -export type StatusCheckExecutorParams = t.TypeOf; +export type StatusCheckParams = t.TypeOf; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx index 62f92fe8a5142..e2e44124ec659 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_monitor_status.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; import * as labels from './translations'; @@ -35,10 +35,6 @@ export const AlertMonitorStatusComponent: React.FC = (p const [newFilters, setNewFilters] = useState([]); - useEffect(() => { - setAlertParams('filters', filters); - }, [filters, setAlertParams]); - return ( <> diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index 77e0c98c0260d..973a3e1d477b6 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import { DataPublicPluginSetup } from 'src/plugins/data/public'; -import { selectMonitorStatusAlert } from '../../../../state/selectors'; +import { selectMonitorStatusAlert, searchTextSelector } from '../../../../state/selectors'; import { AlertMonitorStatusComponent } from '../index'; interface Props { @@ -29,6 +29,12 @@ export const AlertMonitorStatus: React.FC = ({ timerange, }) => { const { filters, locations } = useSelector(selectMonitorStatusAlert); + const searchText = useSelector(searchTextSelector); + + useEffect(() => { + setAlertParams('search', searchText); + }, [setAlertParams, searchText]); + return ( = ({ updatedFieldValues.values ); - useEffect(() => { - if (updatedFieldValues.fieldName === 'observer.geo.name') { - setAlertParams('locations', updatedFieldValues.values); - } - }, [setAlertParams, updatedFieldValues]); + const [filters, setFilters] = useState({ + 'observer.geo.name': selectedLocations, + 'url.port': selectedPorts, + tags: selectedTags, + 'monitor.type': selectedSchemes, + }); useEffect(() => { - setAlertParams('locations', []); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + setAlertParams('filters', filters); + }, [filters, setAlertParams]); const onFilterFieldChange = (fieldName: string, values: string[]) => { + setFilters({ + ...filters, + [fieldName]: values, + }); setUpdatedFieldValues({ fieldName, values }); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx index aabc6fd6e6623..e3893845862fb 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx @@ -51,7 +51,8 @@ export const TimeExpressionSelect: React.FC = ({ setAlertParams }) => { useEffect(() => { const timerangeUnit = timerangeUnitOptions.find(({ checked }) => checked === 'on')?.key ?? 'm'; - setAlertParams('timerange', { from: `now-${numUnits}${timerangeUnit}`, to: 'now' }); + setAlertParams('timerangeUnit', timerangeUnit); + setAlertParams('timerangeCount', numUnits); }, [numUnits, timerangeUnitOptions, setAlertParams]); return ( diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx index a63dbfdecef42..5c0ee632a2bda 100644 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { uniqueId, startsWith } from 'lodash'; import { EuiCallOut } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { Typeahead } from './typeahead'; -import { useUrlParams } from '../../../hooks'; +import { useSearchText, useUrlParams } from '../../../hooks'; import { esKuery, IIndexPattern, @@ -45,6 +45,7 @@ export function KueryBar({ 'data-test-subj': dataTestSubj, }: Props) { const { loading, index_pattern: indexPattern } = useIndexPattern(); + const { updateSearchText } = useSearchText(); const [state, setState] = useState({ suggestions: [], @@ -56,6 +57,10 @@ export function KueryBar({ const [getUrlParams, updateUrlParams] = useUrlParams(); const { search: kuery } = getUrlParams(); + useEffect(() => { + updateSearchText(kuery); + }, [kuery, updateSearchText]); + const indexPatternMissing = loading && !indexPattern; async function onChange(inputValue: string, selectionStart: number) { @@ -63,6 +68,8 @@ export function KueryBar({ return; } + updateSearchText(inputValue); + setIsLoadingSuggestions(true); setState({ ...state, suggestions: [] }); diff --git a/x-pack/plugins/uptime/public/hooks/index.ts b/x-pack/plugins/uptime/public/hooks/index.ts index b92d2d4cf7df5..14264710f7a0d 100644 --- a/x-pack/plugins/uptime/public/hooks/index.ts +++ b/x-pack/plugins/uptime/public/hooks/index.ts @@ -9,3 +9,4 @@ export * from './use_url_params'; export * from './use_telemetry'; export * from './update_kuery_string'; export * from './use_cert_status'; +export * from './use_search_text'; diff --git a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts index 492d2bab5bb80..8a9e134e98bfd 100644 --- a/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts +++ b/x-pack/plugins/uptime/public/hooks/update_kuery_string.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineFiltersAndUserSearch, stringifyKueries } from '../lib/helper'; import { esKuery, IIndexPattern } from '../../../../../src/plugins/data/public'; +import { combineFiltersAndUserSearch, stringifyKueries } from '../../common/lib'; const getKueryString = (urlFilters: string): string => { let kueryString = ''; diff --git a/x-pack/plugins/uptime/public/hooks/use_search_text.ts b/x-pack/plugins/uptime/public/hooks/use_search_text.ts new file mode 100644 index 0000000000000..8226c2365b767 --- /dev/null +++ b/x-pack/plugins/uptime/public/hooks/use_search_text.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setSearchTextAction } from '../state/actions'; +import { searchTextSelector } from '../state/selectors'; + +export const useSearchText = () => { + const dispatch = useDispatch(); + const searchText = useSelector(searchTextSelector); + + const updateSearchText = useCallback( + (nextSearchText: string) => dispatch(setSearchTextAction(nextSearchText)), + [dispatch] + ); + + return { searchText, updateSearchText }; +}; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index b06a7cc93f628..098a999b0d89c 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -12,12 +12,9 @@ describe('monitor status alert type', () => { beforeEach(() => { params = { - locations: [], numTimes: 5, - timerange: { - from: 'now-15m', - to: 'now', - }, + timerangeCount: 15, + timerangeUnit: 'm', }; }); @@ -27,9 +24,9 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/locations: Array", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeCount: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeUnit: string", ], }, } @@ -37,88 +34,21 @@ describe('monitor status alert type', () => { }); describe('timerange', () => { - it('is undefined', () => { - delete params.timerange; - expect(validate(params)).toMatchInlineSnapshot(` - Object { - "errors": Object { - "typeCheckFailure": "Provided parameters do not conform to the expected type.", - "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }", - ], - }, - } - `); - }); - - it('is missing `from` or `to` value', () => { - expect( - validate({ - ...params, - timerange: {}, - }) - ).toMatchInlineSnapshot(` - Object { - "errors": Object { - "typeCheckFailure": "Provided parameters do not conform to the expected type.", - "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }/from: string", - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/timerange: { from: string, to: string }/to: string", - ], - }, - } - `); - }); - - it('is invalid timespan', () => { - expect( - validate({ - ...params, - timerange: { - from: 'now', - to: 'now-15m', - }, - }) - ).toMatchInlineSnapshot(` - Object { - "errors": Object { - "invalidTimeRange": "Time range start cannot exceed time range end", - }, - } - `); - }); - - it('has unparseable `from` value', () => { - expect( - validate({ - ...params, - timerange: { - from: 'cannot parse this to a date', - to: 'now', - }, - }) - ).toMatchInlineSnapshot(` + it('has invalid timerangeCount value', () => { + expect(validate({ ...params, timerangeCount: 0 })).toMatchInlineSnapshot(` Object { "errors": Object { - "timeRangeStartValueNaN": "Specified time range \`from\` is an invalid value", + "invalidTimeRangeValue": "Time range value must be greater than 0", }, } `); }); - it('has unparseable `to` value', () => { - expect( - validate({ - ...params, - timerange: { - from: 'now-15m', - to: 'cannot parse this to a date', - }, - }) - ).toMatchInlineSnapshot(` + it('has NaN timerangeCount value', () => { + expect(validate({ ...params, timerangeCount: NaN })).toMatchInlineSnapshot(` Object { "errors": Object { - "timeRangeEndValueNaN": "Specified time range \`to\` is an invalid value", + "timeRangeStartValueNaN": "Specified time range value must be a number", }, } `); @@ -133,7 +63,7 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value undefined supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", + "Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", ], }, } @@ -146,7 +76,7 @@ describe('monitor status alert type', () => { "errors": Object { "typeCheckFailure": "Provided parameters do not conform to the expected type.", "typeCheckParsingMessage": Array [ - "Invalid value \\"this isn't a number\\" supplied to : (Partial<{ filters: string }> & { locations: Array, numTimes: number, timerange: { from: string, to: string } })/1: { locations: Array, numTimes: number, timerange: { from: string, to: string } }/numTimes: number", + "Invalid value \\"this isn't a number\\" supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array, observer.geo.name: Array, tags: Array, url.port: Array } }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number", ], }, } diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 08fc044bee201..a39317f8db1ed 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -4,51 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PathReporter } from 'io-ts/lib/PathReporter'; import React from 'react'; -import DateMath from '@elastic/datemath'; import { isRight } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; -import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; +import { AtomicStatusCheckParamsType } from '../../../common/runtime_types'; import { MonitorStatusTitle } from './monitor_status_title'; import { CLIENT_ALERT_TYPES } from '../../../common/constants'; import { MonitorStatusTranslations } from './translations'; -export const validate = (alertParams: any) => { +export const validate = (alertParams: unknown) => { const errors: Record = {}; - const decoded = StatusCheckExecutorParamsType.decode(alertParams); + const decoded = AtomicStatusCheckParamsType.decode(alertParams); - /* - * When the UI initially loads, this validate function is called with an - * empty set of params, we don't want to type check against that. - */ if (!isRight(decoded)) { errors.typeCheckFailure = 'Provided parameters do not conform to the expected type.'; errors.typeCheckParsingMessage = PathReporter.report(decoded); - } - - if (isRight(decoded)) { - const { numTimes, timerange } = decoded.right; - const { from, to } = timerange; - const fromAbs = DateMath.parse(from)?.valueOf(); - const toAbs = DateMath.parse(to)?.valueOf(); - if (!fromAbs || isNaN(fromAbs)) { - errors.timeRangeStartValueNaN = 'Specified time range `from` is an invalid value'; - } - if (!toAbs || isNaN(toAbs)) { - errors.timeRangeEndValueNaN = 'Specified time range `to` is an invalid value'; - } - - // the default values for this test will pass, we only want to specify an error - // in the case that `from` is more recent than `to` - if ((fromAbs ?? 0) > (toAbs ?? 1)) { - errors.invalidTimeRange = 'Time range start cannot exceed time range end'; - } - + } else { + const { numTimes, timerangeCount } = decoded.right; if (numTimes < 1) { errors.invalidNumTimes = 'Number of alert check down times must be an integer greater than 0'; } + if (isNaN(timerangeCount)) { + errors.timeRangeStartValueNaN = 'Specified time range value must be a number'; + } + if (timerangeCount <= 0) { + errors.invalidTimeRangeValue = 'Time range value must be greater than 0'; + } } return { errors }; diff --git a/x-pack/plugins/uptime/public/lib/helper/index.ts b/x-pack/plugins/uptime/public/lib/helper/index.ts index e2aa4a2b3d429..cf49328141b83 100644 --- a/x-pack/plugins/uptime/public/lib/helper/index.ts +++ b/x-pack/plugins/uptime/public/lib/helper/index.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { combineFiltersAndUserSearch } from './combine_filters_and_user_search'; export { convertMicrosecondsToMilliseconds } from './convert_measurements'; export * from './observability_integration'; export { getChartDateLabel } from './charts'; export { seriesHasDownValues } from './series_has_down_values'; -export { stringifyKueries } from './stringify_kueries'; export { UptimeUrlParams, getSupportedUrlParams } from './url_params'; diff --git a/x-pack/plugins/uptime/public/state/actions/ui.ts b/x-pack/plugins/uptime/public/state/actions/ui.ts index 0d21e177d3e40..04ad6c2fa0bf3 100644 --- a/x-pack/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/plugins/uptime/public/state/actions/ui.ts @@ -20,6 +20,8 @@ export const setBasePath = createAction('SET BASE PATH'); export const setEsKueryString = createAction('SET ES KUERY STRING'); +export const setSearchTextAction = createAction('SET SEARCH'); + export const toggleIntegrationsPopover = createAction( 'TOGGLE INTEGRATION POPOVER STATE' ); diff --git a/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap b/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap index f8faf78fbc504..c11b146101d35 100644 --- a/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap +++ b/x-pack/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap @@ -9,6 +9,7 @@ Object { "id": "popover-2", "open": true, }, + "searchText": "", } `; @@ -18,5 +19,6 @@ Object { "basePath": "yyz", "esKuery": "", "integrationsPopoverOpen": null, + "searchText": "", } `; diff --git a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts index 94bc626088c84..3b8447ec2d713 100644 --- a/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts +++ b/x-pack/plugins/uptime/public/state/reducers/__tests__/ui.test.ts @@ -18,6 +18,7 @@ describe('ui reducer', () => { basePath: 'abc', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -36,6 +37,7 @@ describe('ui reducer', () => { basePath: '', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -51,6 +53,7 @@ describe('ui reducer', () => { basePath: '', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, action ) @@ -60,6 +63,7 @@ describe('ui reducer', () => { "basePath": "", "esKuery": "", "integrationsPopoverOpen": null, + "searchText": "", } `); }); diff --git a/x-pack/plugins/uptime/public/state/reducers/ui.ts b/x-pack/plugins/uptime/public/state/reducers/ui.ts index 9e7bc2ad02723..3cf4ae9c0bbf2 100644 --- a/x-pack/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/plugins/uptime/public/state/reducers/ui.ts @@ -13,6 +13,7 @@ import { UiPayload, setAlertFlyoutType, setAlertFlyoutVisible, + setSearchTextAction, } from '../actions'; export interface UiState { @@ -20,6 +21,7 @@ export interface UiState { alertFlyoutType?: string; basePath: string; esKuery: string; + searchText: string; integrationsPopoverOpen: PopoverState | null; } @@ -27,6 +29,7 @@ const initialState: UiState = { alertFlyoutVisible: false, basePath: '', esKuery: '', + searchText: '', integrationsPopoverOpen: null, }; @@ -56,6 +59,11 @@ export const uiReducer = handleActions( ...state, alertFlyoutType: action.payload, }), + + [String(setSearchTextAction)]: (state, action: Action) => ({ + ...state, + searchText: action.payload, + }), }, initialState ); diff --git a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts index d8121e29d0cae..2eb0f1e8cb0ee 100644 --- a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -44,6 +44,7 @@ describe('state selectors', () => { basePath: 'yyz', esKuery: '', integrationsPopoverOpen: null, + searchText: '', }, monitorStatus: { status: null, diff --git a/x-pack/plugins/uptime/public/state/selectors/index.ts b/x-pack/plugins/uptime/public/state/selectors/index.ts index ce295faaf5763..b088c346ad811 100644 --- a/x-pack/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/plugins/uptime/public/state/selectors/index.ts @@ -84,3 +84,5 @@ export const monitorListSelector = ({ monitorList }: AppState) => monitorList; export const overviewFiltersSelector = ({ overviewFilters }: AppState) => overviewFilters; export const esKuerySelector = ({ ui: { esKuery } }: AppState) => esKuery; + +export const searchTextSelector = ({ ui: { searchText } }: AppState) => searchText; diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index 5ffc71945caef..8d26838811262 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -23,7 +23,7 @@ export type APICaller = ( export type UMElasticsearchQueryFn = ( params: { callES: APICaller; dynamicSettings: DynamicSettings } & P -) => Promise | R; +) => Promise; export type UMSavedObjectsQueryFn = ( client: SavedObjectsClientContract | ISavedObjectsRepository, diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 8c487c85c5720..6cd836525c077 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -6,9 +6,11 @@ import { contextMessage, - uniqueMonitorIds, - statusCheckAlertFactory, fullListByIdAndLocation, + genFilterString, + hasFilters, + statusCheckAlertFactory, + uniqueMonitorIds, } from '../status_check'; import { GetMonitorStatusResult } from '../../requests'; import { AlertType } from '../../../../../alerts/server'; @@ -310,9 +312,12 @@ describe('status check alert', () => { expect(Object.keys(alert.validate?.params?.props ?? {})).toMatchInlineSnapshot(` Array [ "filters", + "locations", "numTimes", + "search", + "timerangeCount", + "timerangeUnit", "timerange", - "locations", ] `); }); @@ -332,6 +337,205 @@ describe('status check alert', () => { }); }); + describe('hasFilters', () => { + it('returns false for undefined filters', () => { + expect(hasFilters()).toBe(false); + }); + + it('returns false for empty filters', () => { + expect( + hasFilters({ + 'monitor.type': [], + 'observer.geo.name': [], + tags: [], + 'url.port': [], + }) + ).toBe(false); + }); + + it('returns true for an object with a filter', () => { + expect( + hasFilters({ + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'us-west'], + tags: [], + 'url.port': [], + }) + ).toBe(true); + }); + }); + + describe('genFilterString', () => { + const mockGetIndexPattern = jest.fn(); + mockGetIndexPattern.mockReturnValue(undefined); + + it('returns `undefined` for no filters or search', async () => { + expect(await genFilterString(mockGetIndexPattern)).toBeUndefined(); + }); + + it('creates a filter string for filters only', async () => { + const res = await genFilterString(mockGetIndexPattern, { + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'us-west'], + tags: [], + 'url.port': [], + }); + expect(res).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-east", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-west", + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + + it('creates a filter string for search only', async () => { + expect(await genFilterString(mockGetIndexPattern, undefined, 'monitor.id: "kibana-dev"')) + .toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "kibana-dev", + }, + }, + ], + }, + } + `); + }); + + it('creates a filter string for filters and string', async () => { + const res = await genFilterString( + mockGetIndexPattern, + { + 'monitor.type': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + tags: [], + 'url.port': [], + }, + 'monitor.id: "kibana-dev"' + ); + expect(res).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-east", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "apj", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "sydney", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "observer.geo.name": "us-west", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "monitor.id": "kibana-dev", + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + }); + describe('uniqueMonitorIds', () => { let items: GetMonitorStatusResult[]; beforeEach(() => { diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 3dd1558f5da91..cd42082b42c84 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -11,11 +11,19 @@ import { i18n } from '@kbn/i18n'; import { AlertExecutorOptions } from '../../../../alerts/server'; import { UptimeAlertTypeFactory } from './types'; import { GetMonitorStatusResult } from '../requests'; -import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; +import { esKuery, IIndexPattern } from '../../../../../../src/plugins/data/server'; +import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common'; +import { + StatusCheckParamsType, + StatusCheckParams, + StatusCheckFilters, + AtomicStatusCheckParamsType, +} from '../../../common/runtime_types'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants'; import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { commonStateTranslations } from './translations'; +import { stringifyKueries, combineFiltersAndUserSearch } from '../../../common/lib'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -124,6 +132,44 @@ export const fullListByIdAndLocation = ( // we might want to make this a parameter in the future const DEFAULT_MAX_MESSAGE_ROWS = 3; +export const hasFilters = (filters?: StatusCheckFilters) => { + if (!filters) return false; + for (const list of Object.values(filters)) { + if (list.length > 0) { + return true; + } + } + return false; +}; + +export const genFilterString = async ( + getIndexPattern: () => Promise, + filters?: StatusCheckFilters, + search?: string +): Promise => { + const filtersExist = hasFilters(filters); + if (!filtersExist && !search) return undefined; + + let filterString: string | undefined; + if (filtersExist) { + filterString = stringifyKueries(new Map(Object.entries(filters ?? {}))); + } + + let combinedString: string | undefined; + if (filterString && search) { + combinedString = combineFiltersAndUserSearch(filterString, search); + } else if (filterString) { + combinedString = filterString; + } else if (search) { + combinedString = search; + } + + return esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(combinedString ?? ''), + await getIndexPattern() + ); +}; + export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.monitorStatus', name: i18n.translate('xpack.uptime.alerts.monitorStatus', { @@ -131,13 +177,28 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = }), validate: { params: schema.object({ - filters: schema.maybe(schema.string()), + filters: schema.maybe( + schema.oneOf([ + schema.object({ + 'monitor.type': schema.maybe(schema.arrayOf(schema.string())), + 'observer.geo.name': schema.maybe(schema.arrayOf(schema.string())), + tags: schema.maybe(schema.arrayOf(schema.string())), + 'url.port': schema.maybe(schema.arrayOf(schema.string())), + }), + schema.string(), + ]) + ), + locations: schema.maybe(schema.arrayOf(schema.string())), numTimes: schema.number(), - timerange: schema.object({ - from: schema.string(), - to: schema.string(), - }), - locations: schema.arrayOf(schema.string()), + search: schema.maybe(schema.string()), + timerangeCount: schema.maybe(schema.number()), + timerangeUnit: schema.maybe(schema.string()), + timerange: schema.maybe( + schema.object({ + from: schema.string(), + to: schema.string(), + }) + ), }), }, defaultActionGroupId: MONITOR_STATUS.id, @@ -174,18 +235,41 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = producer: 'uptime', async executor(options: AlertExecutorOptions) { const { params: rawParams } = options; - const decoded = StatusCheckExecutorParamsType.decode(rawParams); - if (!isRight(decoded)) { + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( + options.services.savedObjectsClient + ); + const atomicDecoded = AtomicStatusCheckParamsType.decode(rawParams); + const decoded = StatusCheckParamsType.decode(rawParams); + let params: StatusCheckParams; + if (isRight(atomicDecoded)) { + const { filters, search, numTimes, timerangeCount, timerangeUnit } = atomicDecoded.right; + const timerange = { from: `now-${String(timerangeCount) + timerangeUnit}`, to: 'now' }; + const filterString = JSON.stringify( + await genFilterString( + () => + libs.requests.getIndexPattern({ + callES: options.services.callCluster, + dynamicSettings, + }), + filters, + search + ) + ); + params = { + timerange, + numTimes, + locations: [], + filters: filterString, + }; + } else if (isRight(decoded)) { + params = decoded.right; + } else { ThrowReporter.report(decoded); return { error: 'Alert param types do not conform to required shape.', }; } - const params = decoded.right; - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - options.services.savedObjectsClient - ); /* This is called `monitorsByLocation` but it's really * monitors by location by status. The query we run to generate this * filters on the status field, so effectively there should be one and only one diff --git a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts index 7902d9a5c8536..7b08752f12249 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts @@ -8,7 +8,7 @@ import { APICaller, CallAPIOptions } from 'src/core/server'; import { UMElasticsearchQueryFn } from '../adapters'; import { IndexPatternsFetcher, IIndexPattern } from '../../../../../../src/plugins/data/server'; -export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, {}> = async ({ +export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, IIndexPattern | undefined> = async ({ callES, dynamicSettings, }) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index 367db924cf1c6..85fc2c3ef9771 100644 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -33,13 +33,14 @@ import { } from '.'; import { GetMonitorStatesResult } from './get_monitor_states'; import { GetSnapshotCountParams } from './get_snapshot_counts'; +import { IIndexPattern } from '../../../../../../src/plugins/data/server'; type ESQ = UMElasticsearchQueryFn; export interface UptimeRequests { getCerts: ESQ; getFilterBar: ESQ; - getIndexPattern: ESQ<{}, {}>; + getIndexPattern: ESQ<{}, IIndexPattern | undefined>; getLatestMonitor: ESQ; getMonitorDurationChart: ESQ; getMonitorDetails: ESQ; From 08974187957c3cd5640ec20e47dbf20dabd6fe01 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 3 Jun 2020 15:23:54 +0100 Subject: [PATCH 13/15] [ML] Adding per_partition_categorization to job interface and schema (#67953) * [ML] Adding per_partition_categorization to job interface and schema * adding new fields to Category interface --- .../plugins/ml/common/types/anomaly_detection_jobs/job.ts | 6 ++++++ x-pack/plugins/ml/common/types/categories.ts | 2 ++ .../ml/server/routes/schemas/anomaly_detectors_schema.ts | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts index bc55c7549c589..c75387a4b410b 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts @@ -50,6 +50,7 @@ export interface AnalysisConfig { latency?: number; multivariate_by_fields?: boolean; summary_count_field_name?: string; + per_partition_categorization?: PerPartitionCategorization; } export interface Detector { @@ -86,3 +87,8 @@ export interface CustomRule { scope?: object; conditions: any[]; } + +export interface PerPartitionCategorization { + enabled: boolean; + stop_on_warn?: boolean; +} diff --git a/x-pack/plugins/ml/common/types/categories.ts b/x-pack/plugins/ml/common/types/categories.ts index 5d4c3eab53ee8..b3655f274b362 100644 --- a/x-pack/plugins/ml/common/types/categories.ts +++ b/x-pack/plugins/ml/common/types/categories.ts @@ -16,6 +16,8 @@ export interface Category { max_matching_length: number; examples: string[]; grok_pattern: string; + partition_field_name?: string; // TODO: make non-optional once fields have been added to the results + partition_field_value?: string; // TODO: make non-optional once fields have been added to the results } export interface Token { diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 88b86de322e3c..de393e002c55b 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -77,6 +77,12 @@ export const analysisConfigSchema = schema.object({ detectors: schema.arrayOf(detectorSchema), influencers: schema.arrayOf(schema.maybe(schema.string())), categorization_field_name: schema.maybe(schema.string()), + per_partition_categorization: schema.maybe( + schema.object({ + enabled: schema.boolean(), + stop_on_warn: schema.maybe(schema.boolean()), + }) + ), }); export const anomalyDetectionJobSchema = { From c718afcbba114fe274d762fd3fef32f5d19f6bba Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 3 Jun 2020 15:24:13 +0100 Subject: [PATCH 14/15] [ML] Fix expanded row bug when job has no datafeed (#68074) --- .../jobs_list/components/job_details/extract_job_details.js | 2 +- .../jobs/jobs_list/components/job_details/job_details.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index 50e5aeeb29dd9..8f89c4a049189 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -133,7 +133,7 @@ export function extractJobDetails(job) { defaultMessage: 'Datafeed', }), position: 'left', - items: filterObjects(job.datafeed_config, true, true), + items: filterObjects(job.datafeed_config || {}, true, true), }; if (job.node) { datafeed.items.push(['node', JSON.stringify(job.node)]); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 0375997b86bb8..56da4f1e0ff84 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -125,7 +125,7 @@ export class JobDetails extends Component { }, ]; - if (showFullDetails) { + if (showFullDetails && datafeed.items.length) { // Datafeed should be at index 2 in tabs array for full details tabs.splice(2, 0, { id: 'datafeed', From d7b830a6bfba6d98206779630fd101cf3f1a0ecf Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 3 Jun 2020 17:42:11 +0300 Subject: [PATCH 15/15] Kibana 7.7.0: Chart split orientation is wrong (#67840) Co-authored-by: Elastic Machine --- .../visualization_migrations.test.ts | 52 ++++++++++++++----- .../saved_objects/visualization_migrations.ts | 46 +++++++++++++++- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index 83d53d27e41fd..d27d021465dc9 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -623,12 +623,12 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, ]; const tableDoc = generateDoc('table', aggs); @@ -656,7 +656,7 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', @@ -681,7 +681,7 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, ]; const tableDoc = generateDoc('table', aggs); @@ -701,12 +701,12 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, { id: '4', @@ -731,15 +731,15 @@ describe('migration visualization', () => { { id: '2', schema: 'split', - params: { foo: 'bar', row: true }, + params: { foo: 'bar' }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false }, + params: { hey: 'ya' }, }, ]; - const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; + const expected = [{}, { foo: 'bar' }, { hey: 'ya' }]; const migrated = migrate(generateDoc('table', aggs)); const actual = JSON.parse(migrated.attributes.visState); @@ -1386,11 +1386,11 @@ describe('migration visualization', () => { doc as Parameters[0], savedObjectMigrationContext ); - const generateDoc = (params: any) => ({ + const generateDoc = (visState: any) => ({ attributes: { title: 'My Vis', description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), + visState: JSON.stringify(visState), uiStateJSON: '{}', version: 1, kibanaSavedObjectMeta: { @@ -1416,7 +1416,7 @@ describe('migration visualization', () => { }, ], }; - const timeSeriesDoc = generateDoc(params); + const timeSeriesDoc = generateDoc({ params }); const migratedtimeSeriesDoc = migrate(timeSeriesDoc); const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; @@ -1453,12 +1453,38 @@ describe('migration visualization', () => { }, ], }; - const timeSeriesDoc = generateDoc(params); + const timeSeriesDoc = generateDoc({ params }); const migratedtimeSeriesDoc = migrate(timeSeriesDoc); const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; expect(migratedParams.gauge_color_rules[1]).toEqual(params.gauge_color_rules[1]); }); + + it('should move "row" field on split chart by a row or column to vis.params', () => { + const visData = { + type: 'area', + aggs: [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + type: 'terms', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + ], + params: {}, + }; + + const migrated = migrate(generateDoc(visData)); + const actual = JSON.parse(migrated.attributes.visState); + + expect(actual.aggs.filter((agg: any) => 'row' in agg.params)).toEqual([]); + expect(actual.params.row).toBeTruthy(); + }); }); describe('7.8.0 tsvb split_color_mode', () => { diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index 71b286cd91a54..27fe722019a27 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -131,6 +131,50 @@ const migrateOperatorKeyTypo: SavedObjectMigrationFn = (doc) => { return doc; }; +/** + * Moving setting wether to do a row or column split to vis.params + * + * @see https://github.com/elastic/kibana/pull/58462/files#diff-ae69fe15b20a5099d038e9bbe2ed3849 + **/ +const migrateSplitByChartRow: SavedObjectMigrationFn = (doc) => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState: any; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (visState && visState.aggs && visState.params) { + let row: boolean | undefined; + + visState.aggs.forEach((agg: any) => { + if (agg.type === 'terms' && agg.schema === 'split' && 'row' in agg.params) { + row = agg.params.row; + + delete agg.params.row; + } + }); + + if (row !== undefined) { + visState.params.row = row; + } + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + + return doc; +}; + // Migrate date histogram aggregation (remove customInterval) const migrateDateHistogramAggregation: SavedObjectMigrationFn = (doc) => { const visStateJSON = get(doc, 'attributes.visState'); @@ -673,6 +717,6 @@ export const visualizationSavedObjectTypeMigrations = { ), '7.3.1': flow>(migrateFiltersAggQueryStringQueries), '7.4.2': flow>(transformSplitFiltersStringToQueryObject), - '7.7.0': flow>(migrateOperatorKeyTypo), + '7.7.0': flow>(migrateOperatorKeyTypo, migrateSplitByChartRow), '7.8.0': flow>(migrateTsvbDefaultColorPalettes), };