From fdd37e3cfb0116772c48fc45c75e8fe61cd55579 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 9 Jul 2021 17:14:37 -0400 Subject: [PATCH 01/31] Fixed description on Ent Home (#105122) --- .../product_selector/product_selector.tsx | 10 ++++++---- .../applications/enterprise_search/index.scss | 14 -------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx index a7eb2424e797a..0dd2b0988b3f4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -19,6 +19,7 @@ import { EuiFlexItem, EuiSpacer, EuiTitle, + EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -59,14 +60,15 @@ export const ProductSelector: React.FC = ({ access }) => { -

+

{i18n.translate('xpack.enterpriseSearch.overview.heading', { defaultMessage: 'Welcome to Elastic Enterprise Search', })}

- -

+ + +

{config.host ? i18n.translate('xpack.enterpriseSearch.overview.subheading', { defaultMessage: 'Select a product to get started.', @@ -75,7 +77,7 @@ export const ProductSelector: React.FC = ({ access }) => { defaultMessage: 'Choose a product to set up and get started.', })}

-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss index 45bf37def1121..4be8d7322b4c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss @@ -21,21 +21,7 @@ &__header { text-align: center; margin: auto; - } - - &__heading { - @include euiBreakpoint('xs', 's') { - font-size: $euiFontSizeXL; - line-height: map-get(map-get($euiTitles, 'm'), 'line-height'); - } - } - - &__subheading { - color: $euiColorMediumShade; - font-size: $euiFontSize; - @include euiBreakpoint('m', 'l', 'xl') { - font-size: $euiFontSizeL; margin-bottom: $euiSizeL; } } From b7f50c279bf9e348096f2d7b9e289ec1acb5ae47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Fri, 9 Jul 2021 17:17:48 -0400 Subject: [PATCH 02/31] [CTI] updates overview cti panel copy (#105074) --- .../overview/components/overview_cti_links/translations.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts index 663ec3a75c902..91abd48eb2b7e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts @@ -51,9 +51,10 @@ export const DANGER_TITLE = i18n.translate( ); export const DANGER_BODY = i18n.translate( - 'xpack.securitySolution.overview.ctiDashboardDangerPanelBody', + 'xpack.securitySolution.overview.ctiDashboardEnableThreatIntel', { - defaultMessage: 'You need to enable module in order to view data from different sources.', + defaultMessage: + 'You need to enable the filebeat threatintel module in order to view data from different sources.', } ); From bcc8ee2532133c39e663f73303187a2345ef24a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Fri, 9 Jul 2021 17:28:15 -0400 Subject: [PATCH 03/31] [CTI] makes dashboard links external (#104979) * [CTI] makes dashboard links external --- .../cti_disabled_module.tsx | 2 +- .../threat_intel_panel_view.tsx | 28 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx index e22fec1861f8b..21a4beca72f3b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx @@ -25,7 +25,7 @@ export const CtiDisabledModuleComponent = () => { title={i18n.DANGER_TITLE} body={i18n.DANGER_BODY} button={ - + {i18n.DANGER_BUTTON} } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx index 4565c16bc2bf6..b34f6e657d39a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx @@ -22,7 +22,6 @@ import { InspectButtonContainer } from '../../../common/components/inspect'; import { HeaderSection } from '../../../common/components/header_section'; import { ID as CTIEventCountQueryId } from '../../containers/overview_cti_links/use_cti_event_counts'; import { CtiListItem } from '../../containers/overview_cti_links/helpers'; -import { LinkButton } from '../../../common/components/links'; import { useKibana } from '../../../common/lib/kibana'; import { CtiInnerPanel } from './cti_inner_panel'; import * as i18n from './translations'; @@ -36,7 +35,7 @@ const DashboardLinkItems = styled(EuiFlexGroup)` `; const Title = styled(EuiFlexItem)` - min-width: 140px; + min-width: 110px; `; const List = styled.ul` @@ -45,12 +44,11 @@ const List = styled.ul` const DashboardRightSideElement = styled(EuiFlexItem)` align-items: flex-end; - max-width: 160px; `; const RightSideLink = styled(EuiLink)` text-align: right; - min-width: 140px; + min-width: 180px; `; interface ThreatIntelPanelViewProps { @@ -96,12 +94,12 @@ export const ThreatIntelPanelView: React.FC = ({ const button = useMemo( () => ( - + - + ), [buttonHref] ); @@ -117,7 +115,11 @@ export const ThreatIntelPanelView: React.FC = ({ color={'primary'} title={i18n.INFO_TITLE} body={i18n.INFO_BODY} - button={{i18n.INFO_BUTTON}} + button={ + + {i18n.INFO_BUTTON} + + } /> ) : null, [isDashboardPluginDisabled, threatIntelDashboardDocLink] @@ -149,9 +151,7 @@ export const ThreatIntelPanelView: React.FC = ({ gutterSize="l" justifyContent="spaceBetween" > - - {title} - + {title} = ({ alignItems="center" justifyContent="flexEnd" > - + {count} - + {path ? ( - {linkCopy} + + {linkCopy} + ) : ( {linkCopy} From 1739b55f94df04b4b99fbc11233cc16d5cef863b Mon Sep 17 00:00:00 2001 From: Constance Date: Fri, 9 Jul 2021 14:31:01 -0700 Subject: [PATCH 04/31] [Enterprise Search] Fix Error Connecting view not displaying for auth issues (#105125) * Fix ent-search authentication to show the error connecting screen Missed this in #103555 * [Misc] updoot handleConnectionError order/spacing to match - why? because i've lost control of my life, probably --- .../server/lib/enterprise_search_request_handler.test.ts | 2 +- .../server/lib/enterprise_search_request_handler.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts index d0e74f3234c14..f9756119b336c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts @@ -403,7 +403,7 @@ describe('EnterpriseSearchRequestHandler', () => { expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, body: 'Cannot authenticate Enterprise Search user', - headers: mockExpectedResponseHeaders, + headers: { ...mockExpectedResponseHeaders, [ERROR_CONNECTING_HEADER]: 'true' }, }); expect(mockLogger.error).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts index 8031fc724f7b3..57b91c2b30c73 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts @@ -283,12 +283,11 @@ export class EnterpriseSearchRequestHandler { handleConnectionError(response: KibanaResponseFactory, e: Error) { const errorMessage = `Error connecting to Enterprise Search: ${e?.message || e.toString()}`; + const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; this.log.error(errorMessage); if (e instanceof Error) this.log.debug(e.stack as string); - const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; - return response.customError({ statusCode: 502, headers, body: errorMessage }); } @@ -298,9 +297,10 @@ export class EnterpriseSearchRequestHandler { */ handleAuthenticationError(response: KibanaResponseFactory) { const errorMessage = 'Cannot authenticate Enterprise Search user'; + const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; this.log.error(errorMessage); - return response.customError({ statusCode: 502, headers: this.headers, body: errorMessage }); + return response.customError({ statusCode: 502, headers, body: errorMessage }); } /** From a05853ab2ad26e5f8fe8654a138c805ba45422be Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Fri, 9 Jul 2021 17:52:17 -0400 Subject: [PATCH 05/31] [APM] Support for data streams index patterns for cloud migration (#105015) * [APM] Support for data streams index patterns for cloud migration (#101095) * [APM] Update apm package version to 0.3.0 (elastic/apm-server/#5579) --- x-pack/plugins/apm/server/index.test.ts | 2 +- x-pack/plugins/apm/server/index.ts | 6 ++- .../get_apm_package_policy_definition.ts | 2 +- .../create_static_index_pattern.ts | 5 ++- x-pack/plugins/apm/server/routes/fleet.ts | 41 ++++++++++++++----- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/apm/server/index.test.ts b/x-pack/plugins/apm/server/index.test.ts index 6052ec921f9f9..56c9825db5a5c 100644 --- a/x-pack/plugins/apm/server/index.test.ts +++ b/x-pack/plugins/apm/server/index.test.ts @@ -30,7 +30,7 @@ describe('mergeConfigs', () => { expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({ 'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*', - 'apm_oss.indexPattern': 'apm-*', + 'apm_oss.indexPattern': 'traces-apm*,logs-apm*,metrics-apm*,apm-*', 'apm_oss.metricsIndices': 'metrics-apm*,apm-*-metric-*', 'apm_oss.spanIndices': 'traces-apm*,apm-*-span-*', 'apm_oss.transactionIndices': 'traces-apm*,apm-*-transaction-*', diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index f14894a76edb4..9031f454f4a7f 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -74,7 +74,7 @@ export function mergeConfigs( 'apm_oss.metricsIndices': apmOssConfig.metricsIndices, 'apm_oss.sourcemapIndices': apmOssConfig.sourcemapIndices, 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices, - 'apm_oss.indexPattern': apmOssConfig.indexPattern, // TODO: add data stream indices: traces-apm*,logs-apm*,metrics-apm*. Blocked by https://github.com/elastic/kibana/issues/87851 + 'apm_oss.indexPattern': apmOssConfig.indexPattern, /* eslint-enable @typescript-eslint/naming-convention */ 'xpack.apm.serviceMapEnabled': apmConfig.serviceMapEnabled, 'xpack.apm.serviceMapFingerprintBucketSize': @@ -119,6 +119,10 @@ export function mergeConfigs( 'apm_oss.metricsIndices' ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`; + mergedConfig[ + 'apm_oss.indexPattern' + ] = `traces-apm*,logs-apm*,metrics-apm*,${mergedConfig['apm_oss.indexPattern']}`; + return mergedConfig; } diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts index 82e85e7da9bb3..291b2fa2af99d 100644 --- a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts +++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts @@ -34,7 +34,7 @@ export function getApmPackagePolicyDefinition( ], package: { name: APM_PACKAGE_NAME, - version: '0.3.0-dev.1', + version: '0.3.0', title: 'Elastic APM', }, }; diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index 607a7e6227a9d..a2944d6241d2d 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -19,7 +19,8 @@ export async function createStaticIndexPattern( setup: Setup, config: APMRouteHandlerResources['config'], savedObjectsClient: InternalSavedObjectsClient, - spaceId: string | undefined + spaceId: string | undefined, + overwrite = false ): Promise { return withApmSpan('create_static_index_pattern', async () => { // don't autocreate APM index pattern if it's been disabled via the config @@ -45,7 +46,7 @@ export async function createStaticIndexPattern( }, { id: APM_STATIC_INDEX_PATTERN_ID, - overwrite: false, + overwrite, namespace: spaceId, } ) diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index 6628d29b256f7..b760014d6af89 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -25,6 +25,8 @@ import { createCloudApmPackgePolicy } from '../lib/fleet/create_cloud_apm_packag import { getUnsupportedApmServerSchema } from '../lib/fleet/get_unsupported_apm_server_schema'; import { isSuperuser } from '../lib/fleet/is_superuser'; import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client'; +import { setupRequest } from '../lib/helpers/setup_request'; +import { createStaticIndexPattern } from '../lib/index_pattern/create_static_index_pattern'; const hasFleetDataRoute = createApmServerRoute({ endpoint: 'GET /api/apm/fleet/has_data', @@ -154,7 +156,7 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy', options: { tags: ['access:apm', 'access:apm_write'] }, handler: async (resources) => { - const { plugins, context, config, request, logger } = resources; + const { plugins, context, config, request, logger, core } = resources; const cloudApmMigrationEnabled = config['xpack.apm.agent.migrations.enabled']; if (!plugins.fleet || !plugins.security) { @@ -171,15 +173,34 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ if (!hasRequiredRole || !cloudApmMigrationEnabled) { throw Boom.forbidden(CLOUD_SUPERUSER_REQUIRED_MESSAGE); } - return { - cloud_apm_package_policy: await createCloudApmPackgePolicy({ - cloudPluginSetup, - fleetPluginStart, - savedObjectsClient, - esClient, - logger, - }), - }; + + const cloudApmAackagePolicy = await createCloudApmPackgePolicy({ + cloudPluginSetup, + fleetPluginStart, + savedObjectsClient, + esClient, + logger, + }); + + const [setup, internalSavedObjectsClient] = await Promise.all([ + setupRequest(resources), + core + .start() + .then(({ savedObjects }) => savedObjects.createInternalRepository()), + ]); + + const spaceId = plugins.spaces?.setup.spacesService.getSpaceId(request); + + // force update the index pattern title with data streams + await createStaticIndexPattern( + setup, + config, + internalSavedObjectsClient, + spaceId, + true + ); + + return { cloud_apm_package_policy: cloudApmAackagePolicy }; }, }); From 937a4f381a592f605b1edc6a898de02e4831aadf Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Fri, 9 Jul 2021 17:20:10 -0500 Subject: [PATCH 06/31] [Security Solution] Enrichment details UI cleanup (#104995) * Remove the "view threat intel data" button from the alert summary This can be accomplished by clicking the tab itself; there's no real need for this button. * Remove section title from alert summary view This made sense when we had both alert and threat sections, but we no longer do. Removes the corresponding translation, and the analogously unused title from the defunct threat summary view. * Smaller spacer on alert summary tab This is distractingly large as compared to other tabs. * Move "no enrichments" panel below our threat details table * Remove old import * Move inspect button inline with rest of header * Add HR separator to top of NoEnrichmentsPanel This should arguably be added a level above so as to keep this panel context-agnostic, but it's currently only used in one place and will always require the HR, so YAGNI for now. * Adds more space between title and description on "no data" panel It has been suggested that the NoEnrichmentsPanel should be following the guidelines of the EuiEmptyPrompt. If we end up needing e.g. centered text, we're better off rewriting NoEnrichmentsPanelView in terms of an EuiEmptyPrompt. * StyledEuiInMemoryTable has no header row height We have never provided column names to this component. However, there is default padding on the thead tds such that even without content they take up vertical height. This has resulted in some extra top-margin on historical uses of this table (which are just the Alert Details views). However, the addition of a sibling table (ThreatSummaryView) made the extra margin noticable, since it made the two tables appear disjointed even though they're right up against each other. This fixes the issue by removing the padding, allowing the thead to take no height. And now that that space isn't taken up by the table header, we need to add a little bit of space between the header and table on the Threat Details view. * Move test to appropriate location The ThreatDetailsView is no longer responsible for displaying the "no data" components, that's now a level above in EventDetails. * Prune unused translations These have been changed in the latest designs. * Only add HR if panel is preceded by enrichments We do not want an HR if there's nothing above the panel. --- .../event_details/alert_summary_view.tsx | 2 +- .../empty_threat_details_view.test.tsx | 48 ---------- .../cti_details/empty_threat_details_view.tsx | 51 ----------- .../cti_details/no_enrichments_panel.test.tsx | 57 ++++++++++++ .../cti_details/no_enrichments_panel.tsx | 88 +++++++++++++++++++ .../cti_details/threat_details_view.test.tsx | 9 -- .../cti_details/threat_details_view.tsx | 18 ++-- .../event_details/cti_details/translations.ts | 34 ++++++- .../event_details/event_details.test.tsx | 7 ++ .../event_details/event_details.tsx | 53 +++++------ .../components/event_details/summary_view.tsx | 7 +- .../components/event_details/translations.ts | 12 --- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 14 files changed, 222 insertions(+), 170 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 3a1a29b63eadf..329b8e32f057d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -213,7 +213,7 @@ const AlertSummaryViewComponent: React.FC<{ return ( <> - + { - const mount = useMountAppended(); - const mockTheme = getMockTheme({ - eui: { - euiBreakpoints: { - l: '1200px', - }, - paddingSizes: { - m: '8px', - xl: '32px', - }, - }, - }); - - test('renders correct items', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); - }); - - test('renders link to docs', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('a').exists()).toEqual(true); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx deleted file mode 100644 index d7e1c4d7754ec..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx +++ /dev/null @@ -1,51 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; - -import { useKibana } from '../../../lib/kibana'; -import * as i18n from './translations'; - -const EmptyThreatDetailsViewContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -const Span = styled.span` - color: ${({ theme }) => theme.eui.euiColorDarkShade}; - line-height: 1.8em; - text-align: center; - padding: ${({ theme }) => `${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.xl}`}; -`; - -const EmptyThreatDetailsViewComponent: React.FC<{}> = () => { - const threatIntelDocsUrl = `${ - useKibana().services.docLinks.links.filebeat.base - }/filebeat-module-threatintel.html`; - - return ( - - - -

{i18n.NO_ENRICHMENT_FOUND}

-
- - {i18n.IF_CTI_NOT_ENABLED} - - {i18n.CHECK_DOCS} - - -
- ); -}; - -EmptyThreatDetailsViewComponent.displayName = 'EmptyThreatDetailsView'; - -export const EmptyThreatDetailsView = React.memo(EmptyThreatDetailsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx new file mode 100644 index 0000000000000..819c666bd7267 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { NoEnrichmentsPanel } from './no_enrichments_panel'; +import * as i18n from './translations'; + +jest.mock('../../../lib/kibana'); + +describe('NoEnrichmentsPanelView', () => { + it('renders a qualified container', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(true); + }); + + it('renders nothing when all enrichments are present', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(false); + }); + + it('renders expected text when no enrichments are present', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_ENRICHMENTS_FOUND_TITLE + ); + }); + + it('renders expected text when existing enrichments are absent', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_INDICATOR_ENRICHMENTS_TITLE + ); + }); + + it('renders expected text when investigation enrichments are absent', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_INVESTIGATION_ENRICHMENTS_TITLE + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx new file mode 100644 index 0000000000000..b521c3ba92c4d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiHorizontalRule, EuiLink, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { useKibana } from '../../../lib/kibana'; + +import * as i18n from './translations'; + +const Container = styled(EuiPanel)` + display: flex; + flex-direction: column; +`; + +const NoEnrichmentsPanelView: React.FC<{ + title: React.ReactNode; + description: React.ReactNode; +}> = ({ title, description }) => { + return ( + + {title} + + + {description} + + + ); +}; + +NoEnrichmentsPanelView.displayName = 'NoEnrichmentsPanelView'; + +export const NoEnrichmentsPanel: React.FC<{ + existingEnrichmentsCount: number; + investigationEnrichmentsCount: number; +}> = ({ existingEnrichmentsCount, investigationEnrichmentsCount }) => { + const threatIntelDocsUrl = `${ + useKibana().services.docLinks.links.filebeat.base + }/filebeat-module-threatintel.html`; + const noIndicatorEnrichmentsDescription = ( + <> + {i18n.IF_CTI_NOT_ENABLED} + + {i18n.CHECK_DOCS} + + + ); + + if (existingEnrichmentsCount === 0 && investigationEnrichmentsCount === 0) { + return ( + {i18n.NO_ENRICHMENTS_FOUND_TITLE}} + description={ + <> +

{noIndicatorEnrichmentsDescription}

+

{i18n.NO_INVESTIGATION_ENRICHMENTS_DESCRIPTION}

+ + } + /> + ); + } else if (existingEnrichmentsCount === 0) { + return ( + <> + + {i18n.NO_INDICATOR_ENRICHMENTS_TITLE}} + description={noIndicatorEnrichmentsDescription} + /> + + ); + } else if (investigationEnrichmentsCount === 0) { + return ( + <> + + {i18n.NO_INVESTIGATION_ENRICHMENTS_TITLE}} + description={i18n.NO_INVESTIGATION_ENRICHMENTS_DESCRIPTION} + /> + + ); + } else { + return null; + } +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx index 0113dde96a4b6..c25457a5e5e88 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx @@ -31,15 +31,6 @@ describe('ThreatDetailsView', () => { ); }); - it('renders an empty view if there are no enrichments', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); - }); - it('renders anchor links for event.url and event.reference', () => { const enrichments = [ buildEventEnrichmentMock({ diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx index d5e985c5757a6..b6b8a47c1dd8c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx @@ -20,7 +20,6 @@ import React, { Fragment } from 'react'; import { StyledEuiInMemoryTable } from '../summary_view'; import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from '../helpers'; -import { EmptyThreatDetailsView } from './empty_threat_details_view'; import { FIRSTSEEN, EVENT_URL, EVENT_REFERENCE } from '../../../../../common/cti/constants'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { getFirstElement } from '../../../../../common/utils/data_retrieval'; @@ -70,15 +69,13 @@ const ThreatDetailsHeader: React.FC<{ + {isInvestigationTimeEnrichment(type) && ( + + + + )}
- {isInvestigationTimeEnrichment(type) && ( - - - - - - )} ); @@ -131,10 +128,6 @@ const buildThreatDetailsItems = (enrichment: CtiEnrichment) => const ThreatDetailsViewComponent: React.FC<{ enrichments: CtiEnrichment[]; }> = ({ enrichments }) => { - if (enrichments.length < 1) { - return ; - } - const sortedEnrichments = enrichments.sort((a, b) => getFirstSeen(b) - getFirstSeen(a)); return ( @@ -146,6 +139,7 @@ const ThreatDetailsViewComponent: React.FC<{ return ( + { ).toEqual('Summary'); }); }); + + describe('threat intel tab', () => { + it('renders a "no enrichments" panel view if there are no enrichments', () => { + alertsWrapper.find('[data-test-subj="threatIntelTab"]').first().simulate('click'); + expect(alertsWrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 9afaaef61b17a..7074212dcdb4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -9,9 +9,6 @@ import { EuiTabbedContent, EuiTabbedContentTab, EuiSpacer, - EuiButton, - EuiFlexGroup, - EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner, } from '@elastic/eui'; @@ -34,6 +31,7 @@ import { parseExistingEnrichments, timelineDataToEnrichment, } from './cti_details/helpers'; +import { NoEnrichmentsPanel } from './cti_details/no_enrichments_panel'; type EventViewTab = EuiTabbedContentTab; @@ -100,9 +98,6 @@ const EventDetailsComponent: React.FC = ({ (tab: EuiTabbedContentTab) => setSelectedTabId(tab.id as EventViewId), [setSelectedTabId] ); - const viewThreatIntelTab = useCallback(() => setSelectedTabId(EventsViewType.threatIntelView), [ - setSelectedTabId, - ]); const eventFields = useMemo(() => getEnrichmentFields(data), [data]); const existingEnrichments = useMemo( @@ -118,6 +113,9 @@ const EventDetailsComponent: React.FC = ({ loading: enrichmentsLoading, result: enrichmentsResponse, } = useInvestigationTimeEnrichment(eventFields); + const investigationEnrichments = useMemo(() => enrichmentsResponse?.enrichments ?? [], [ + enrichmentsResponse?.enrichments, + ]); const allEnrichments = useMemo(() => { if (enrichmentsLoading || !enrichmentsResponse?.enrichments) { return existingEnrichments; @@ -140,29 +138,20 @@ const EventDetailsComponent: React.FC = ({ eventId: id, browserFields, timelineId, - title: i18n.ALERT_SUMMARY, }} /> + {enrichmentCount > 0 && ( + + )} {enrichmentsLoading && ( <> )} - {enrichmentCount > 0 && ( - <> - - - - - {i18n.VIEW_CTI_DATA} - - - - )} ), } @@ -176,7 +165,6 @@ const EventDetailsComponent: React.FC = ({ enrichmentsLoading, enrichmentCount, allEnrichments, - viewThreatIntelTab, ] ); @@ -192,10 +180,25 @@ const EventDetailsComponent: React.FC = ({ {enrichmentsLoading ? : `(${enrichmentCount})`} ), - content: , + content: ( + <> + + + + ), } : undefined, - [allEnrichments, enrichmentCount, enrichmentsLoading, isAlert] + [ + allEnrichments, + enrichmentCount, + enrichmentsLoading, + existingEnrichments.length, + investigationEnrichments.length, + isAlert, + ] ); const tableTab = useMemo( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index 0e846f3f6f699..961860ed6d8b9 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -13,12 +13,13 @@ import { SummaryRow } from './helpers'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` - .euiTableHeaderCell { - border: none; - } + .euiTableHeaderCell, .euiTableRowCell { border: none; } + .euiTableHeaderCell .euiTableCellContent { + padding: 0; + } `; const StyledEuiTitle = styled(EuiTitle)` diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index a17ca5e434ace..c632f5d6332e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -11,22 +11,10 @@ export const SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.summa defaultMessage: 'Summary', }); -export const ALERT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.alertSummary', { - defaultMessage: 'Alert Summary', -}); - export const THREAT_INTEL = i18n.translate('xpack.securitySolution.alertDetails.threatIntel', { defaultMessage: 'Threat Intel', }); -export const THREAT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.threatSummary', { - defaultMessage: 'Threat Summary', -}); - -export const VIEW_CTI_DATA = i18n.translate('xpack.securitySolution.alertDetails.threatIntelCta', { - defaultMessage: 'View threat intel data', -}); - export const INVESTIGATION_GUIDE = i18n.translate( 'xpack.securitySolution.alertDetails.summary.investigationGuide', { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 387547295f047..2b1088d8c11ae 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18562,16 +18562,13 @@ "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", "xpack.securitySolution.administration.os.windows": "Windows", - "xpack.securitySolution.alertDetails.alertSummary": "アラート概要", "xpack.securitySolution.alertDetails.checkDocs": "マニュアルをご確認ください。", "xpack.securitySolution.alertDetails.ifCtiNotEnabled": "脅威インテリジェンスソースを有効にしていない場合で、この機能について関心がある場合は、", - "xpack.securitySolution.alertDetails.noEnrichmentFound": "Threat Intel Enrichmentが見つかりません", "xpack.securitySolution.alertDetails.summary": "まとめ", "xpack.securitySolution.alertDetails.summary.investigationGuide": "調査ガイド", "xpack.securitySolution.alertDetails.summary.readLess": "表示を減らす", "xpack.securitySolution.alertDetails.summary.readMore": "続きを読む", "xpack.securitySolution.alertDetails.threatIntel": "Threat Intel", - "xpack.securitySolution.alertDetails.threatSummary": "脅威概要", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "このルールで生成されたすべてのアラートのリスクスコアを選択します。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "デフォルトリスクスコア", "xpack.securitySolution.alerts.riskScoreMapping.mappingDescriptionLabel": "ソースイベント値を使用して、デフォルトリスクスコアを上書きします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d142969022c81..04394a1ac1704 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18826,16 +18826,13 @@ "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", "xpack.securitySolution.administration.os.windows": "Windows", - "xpack.securitySolution.alertDetails.alertSummary": "告警摘要", "xpack.securitySolution.alertDetails.checkDocs": "请查看我们的文档。", "xpack.securitySolution.alertDetails.ifCtiNotEnabled": "如果尚未启用任何威胁情报来源,并希望更多了解此功能,", - "xpack.securitySolution.alertDetails.noEnrichmentFound": "未找到威胁情报扩充", "xpack.securitySolution.alertDetails.summary": "摘要", "xpack.securitySolution.alertDetails.summary.investigationGuide": "调查指南", "xpack.securitySolution.alertDetails.summary.readLess": "阅读更少内容", "xpack.securitySolution.alertDetails.summary.readMore": "阅读更多内容", "xpack.securitySolution.alertDetails.threatIntel": "威胁情报", - "xpack.securitySolution.alertDetails.threatSummary": "威胁摘要", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "选择此规则生成的所有告警的风险分数。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "默认风险分数", "xpack.securitySolution.alerts.riskScoreMapping.mappingDescriptionLabel": "使用源事件值覆盖默认风险分数。", From c07f51e5be9b16c58a4988491d8ae2ce021b4aba Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 9 Jul 2021 16:23:00 -0600 Subject: [PATCH 07/31] [Security Detections] Fixes ip on threshold preview button when selecting an ip data type such as source.ip (#105126) ## Summary See https://github.com/elastic/kibana/issues/100433 for details and test instructions. This is considered critical and a small fix for 7.14.0 has been requested. * Wrote Cypress test that exercises the bug * Fixed mutation in one part of the Cypress Test * Decided to remove the "missing" that we were telling users was "others" since missing is not the same as others. It no longer errors, but some users might be asking why we don't show "others" anymore. The reality is that we only showed "missing" which isn't adding value to the preview of what detections will end up looking like. * Later if we want a true "others" we should implement it as a larger feature request and not a bug fix IMHO Before you would get errors in your network panel: ![errors_threshold](https://user-images.githubusercontent.com/1151048/125126681-b0380e00-e0b8-11eb-9f2c-a75e2909754c.png) After you now get the `source.ip` without errors: Screen Shot 2021-07-09 at 1 28 24 PM ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../detection_rules/threshold_rule.spec.ts | 27 ++++++++++++++++--- .../cypress/tasks/create_new_rule.ts | 2 +- .../components/rules/query_preview/index.tsx | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index ad71d54eb2a7a..ce00c9b40aead 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -6,7 +6,7 @@ */ import { formatMitreAttackDescription } from '../../helpers/rules'; -import { indexPatterns, newRule, newThresholdRule } from '../../objects/rule'; +import { indexPatterns, newRule, newThresholdRule, ThresholdRule } from '../../objects/rule'; import { ALERT_RULE_METHOD, @@ -180,9 +180,9 @@ describe('Detection rules, threshold', () => { cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); }); - it('Preview results', () => { - const previewRule = { ...newThresholdRule }; - previewRule.index!.push('.siem-signals*'); + it('Preview results of keyword using "host.name"', () => { + const previewRule: ThresholdRule = { ...newThresholdRule }; + previewRule.index = [...previewRule.index, '.siem-signals*']; createCustomRuleActivated(newRule); goToManageAlertsDetectionRules(); @@ -194,4 +194,23 @@ describe('Detection rules, threshold', () => { cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '3 unique hits'); }); + + it('Preview results of "ip" using "source.ip"', () => { + const previewRule: ThresholdRule = { + ...newThresholdRule, + thresholdField: 'source.ip', + threshold: '1', + }; + previewRule.index = [...previewRule.index, '.siem-signals*']; + + createCustomRuleActivated(newRule); + goToManageAlertsDetectionRules(); + waitForRulesTableToBeLoaded(); + goToCreateNewRule(); + selectThresholdRuleType(); + fillDefineThresholdRule(previewRule); + previewResults(); + + cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '10 unique hits'); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 9b74110f0ef77..1b420cd6d1520 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -275,7 +275,7 @@ export const fillDefineThresholdRule = (rule: ThresholdRule) => { cy.get(TIMELINE(rule.timeline.id!)).click(); cy.get(COMBO_BOX_CLEAR_BTN).click(); - rule.index!.forEach((index) => { + rule.index.forEach((index) => { cy.get(COMBO_BOX_INPUT).first().type(`${index}{enter}`); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx index 6342d468f5962..45b66058a04fb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx @@ -118,6 +118,7 @@ export const PreviewQuery = ({ startDate: toTime, filterQuery: queryFilter, indexNames: index, + includeMissingData: false, histogramType: MatrixHistogramType.events, stackByField: 'event.category', threshold: ruleType === 'threshold' ? threshold : undefined, From b40fc09dfca3c2e7072ba33f65c9c4ee7474764c Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 16:40:13 -0700 Subject: [PATCH 08/31] skip another suite blocking es promotion (#104466) --- test/functional/apps/discover/_large_string.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index de3f0f2c40ae1..ea219881c7a95 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover']); - describe('test large strings', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('test large strings', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); From d776c0940ee47098bef21a129a03445e5fbf3eda Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 18:01:11 -0700 Subject: [PATCH 09/31] skip all discover functional tests to unblock es promotion (#104466) --- test/functional/apps/discover/_data_grid_field_data.ts | 3 +-- test/functional/apps/discover/_field_data.ts | 3 +-- test/functional/apps/discover/_field_data_with_fields_api.ts | 3 +-- test/functional/apps/discover/_large_string.ts | 3 +-- test/functional/apps/discover/index.ts | 3 ++- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index cf8a6fc1bd60f..94e8e942f86ba 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; const dataGrid = getService('dataGrid'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover data grid field data tests', function describeIndexTests() { + describe('discover data grid field data tests', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index 6471b751945a8..ec9f9cf65e0fa 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -20,8 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover tab', function describeIndexTests() { + describe('discover tab', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 7c6867e935063..110e255d18c75 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -20,8 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover tab with new fields API', function describeIndexTests() { + describe('discover tab with new fields API', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index ea219881c7a95..de3f0f2c40ae1 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover']); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('test large strings', function () { + describe('test large strings', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index b396f172f6961..a17bf53e7f478 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -12,7 +12,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('discover app', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover app', function () { this.tags('ciGroup6'); before(function () { From c0fec48104a7d026802673723ffb8796211bc2e0 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 9 Jul 2021 20:55:20 -0600 Subject: [PATCH 10/31] [Security Solutions][Detection Engine] Fixes button group alignments in machine learning and tags (#105166) ## Summary See: https://github.com/elastic/kibana/issues/104055 For more issue details. This is deemed embarrassing enough to be critical for a fix for 7.14.0 before shipping. EUI looks to have updated its self and added a new attribute that it wants us to use called `numFIlters` which when set will show the total number of filter items before they are selected. Once selected the number and look and feel change. ```ts numFilters ``` Before: Screen Shot 2021-07-09 at 5 45 08 PM After before selections: Screen Shot 2021-07-09 at 5 48 43 PM After once you have selections: Screen Shot 2021-07-09 at 5 49 44 PM Before: Screen Shot 2021-07-09 at 5 42 01 PM After before selections: Screen Shot 2021-07-09 at 5 42 27 PM After once you have selections: Screen Shot 2021-07-09 at 5 49 36 PM ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../filters/__snapshots__/groups_filter_popover.test.tsx.snap | 1 + .../ml_popover/jobs_table/filters/groups_filter_popover.tsx | 1 + .../rules/all/rules_table_filters/tags_filter_popover.tsx | 2 ++ 3 files changed, 4 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap index 22805d34d2ee1..410fb7f3ae793 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap @@ -10,6 +10,7 @@ exports[`GroupsFilterPopover renders correctly against snapshot 1`] = ` iconType="arrowDown" isSelected={false} numActiveFilters={0} + numFilters={3} onClick={[Function]} > Groups diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx index b7425a62f6773..249dc0dfccdbb 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx @@ -59,6 +59,7 @@ export const GroupsFilterPopoverComponent = ({ iconType="arrowDown" onClick={() => setIsGroupPopoverOpen(!isGroupPopoverOpen)} isSelected={isGroupPopoverOpen} + numFilters={uniqueGroups.length} hasActiveFilters={selectedGroups.length > 0} numActiveFilters={selectedGroups.length} > diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx index 45ce5bc18361c..c5262caf6c776 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx @@ -102,9 +102,11 @@ const TagsFilterPopoverComponent = ({ ownFocus button={ setIsTagPopoverOpen(!isTagPopoverOpen)} + numFilters={tags.length} isSelected={isTagPopoverOpen} hasActiveFilters={selectedTags.length > 0} numActiveFilters={selectedTags.length} From 857dc9f2e1e1edd86c9cf7cd5f157e3e8b82a14a Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Sat, 10 Jul 2021 09:27:07 +0200 Subject: [PATCH 11/31] [APM] Don't log error if request was aborted (#105024) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/register_routes/index.test.ts | 23 ++++-- .../server/routes/register_routes/index.ts | 72 ++++++++++++------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts index 158d7ee7e76a3..b9dece866fae5 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts +++ b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts @@ -109,6 +109,11 @@ const initApi = ( params: {}, query: {}, body: null, + events: { + aborted$: { + toPromise: () => new Promise(() => {}), + }, + }, ...request, }, responseMock @@ -202,7 +207,7 @@ describe('createApi', () => { describe('when validating', () => { describe('_inspect', () => { it('allows _inspect=true', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -234,7 +239,7 @@ describe('createApi', () => { }); it('rejects _inspect=1', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, @@ -267,7 +272,7 @@ describe('createApi', () => { }); it('allows omitting _inspect', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, @@ -297,7 +302,11 @@ describe('createApi', () => { simulateRequest, mocks: { response }, } = initApi([ - { endpoint: 'GET /foo', options: { tags: [] }, handler: jest.fn() }, + { + endpoint: 'GET /foo', + options: { tags: [] }, + handler: jest.fn().mockResolvedValue({}), + }, ]); await simulateRequest({ @@ -328,7 +337,7 @@ describe('createApi', () => { }); it('validates path parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -402,7 +411,7 @@ describe('createApi', () => { }); it('validates body parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -448,7 +457,7 @@ describe('createApi', () => { }); it('validates query parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.ts b/x-pack/plugins/apm/server/routes/register_routes/index.ts index 136f3c73d8046..8e6070de722be 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.ts +++ b/x-pack/plugins/apm/server/routes/register_routes/index.ts @@ -29,6 +29,13 @@ const inspectRt = t.exact( }) ); +const CLIENT_CLOSED_REQUEST = { + statusCode: 499, + body: { + message: 'Client closed request', + }, +}; + export const inspectableEsQueriesMap = new WeakMap< KibanaRequest, InspectResponse @@ -89,23 +96,40 @@ export function registerRoutes({ runtimeType ); - const data: Record | undefined | null = (await handler({ - request, - context, - config, - logger, - core, - plugins, - params: merge( - { - query: { - _inspect: false, + const { aborted, data } = await Promise.race([ + handler({ + request, + context, + config, + logger, + core, + plugins, + params: merge( + { + query: { + _inspect: false, + }, }, - }, - validatedParams - ), - ruleDataClient, - })) as any; + validatedParams + ), + ruleDataClient, + }).then((value) => { + return { + aborted: false, + data: value as Record | undefined | null, + }; + }), + request.events.aborted$.toPromise().then(() => { + return { + aborted: true, + data: undefined, + }; + }), + ]); + + if (aborted) { + return response.custom(CLIENT_CLOSED_REQUEST); + } if (Array.isArray(data)) { throw new Error('Return type cannot be an array'); @@ -118,9 +142,6 @@ export function registerRoutes({ } : { ...data }; - // cleanup - inspectableEsQueriesMap.delete(request); - if (!options.disableTelemetry && telemetryUsageCounter) { telemetryUsageCounter.incrementCounter({ counterName: `${method.toUpperCase()} ${pathname}`, @@ -131,6 +152,7 @@ export function registerRoutes({ return response.ok({ body }); } catch (error) { logger.error(error); + if (!options.disableTelemetry && telemetryUsageCounter) { telemetryUsageCounter.incrementCounter({ counterName: `${method.toUpperCase()} ${pathname}`, @@ -147,16 +169,18 @@ export function registerRoutes({ }, }; - if (Boom.isBoom(error)) { - opts.statusCode = error.output.statusCode; + if (error instanceof RequestAbortedError) { + return response.custom(merge(opts, CLIENT_CLOSED_REQUEST)); } - if (error instanceof RequestAbortedError) { - opts.statusCode = 499; - opts.body.message = 'Client closed request'; + if (Boom.isBoom(error)) { + opts.statusCode = error.output.statusCode; } return response.custom(opts); + } finally { + // cleanup + inspectableEsQueriesMap.delete(request); } } ); From e4ba52928c289547d97706b083bed1202be1176d Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Sat, 10 Jul 2021 18:34:03 -0500 Subject: [PATCH 12/31] [Security Solution] remove query strategy v1 (#104196) --- .../common/endpoint/types/index.ts | 14 - .../management/pages/endpoint_hosts/mocks.ts | 4 - .../pages/endpoint_hosts/store/builders.ts | 1 - .../pages/endpoint_hosts/store/middleware.ts | 3 +- .../store/mock_endpoint_result_list.ts | 16 +- .../pages/endpoint_hosts/store/reducer.ts | 2 - .../pages/endpoint_hosts/store/selectors.ts | 7 - .../management/pages/endpoint_hosts/types.ts | 3 - .../pages/endpoint_hosts/view/index.test.tsx | 34 +- .../pages/endpoint_hosts/view/index.tsx | 9 +- .../endpoint/endpoint_app_context_services.ts | 54 --- .../endpoint/routes/actions/isolation.ts | 2 +- .../routes/metadata/enrichment.test.ts | 86 +--- .../endpoint/routes/metadata/handlers.ts | 85 +--- .../server/endpoint/routes/metadata/index.ts | 27 +- .../endpoint/routes/metadata/metadata.test.ts | 107 +--- .../routes/metadata/metadata_v1.test.ts | 456 ------------------ .../routes/metadata/query_builders.test.ts | 73 ++- .../routes/metadata/query_builders.ts | 24 +- .../routes/metadata/query_builders_v1.test.ts | 188 -------- .../metadata/support/query_strategies.ts | 123 ++--- .../routes/metadata/support/test_support.ts | 56 --- .../server/endpoint/services/metadata.ts | 13 +- .../server/endpoint/types.ts | 15 +- .../factory/hosts/details/helpers.ts | 14 +- .../apis/index.ts | 1 - .../apis/metadata.ts | 9 - .../apis/metadata_v1.ts | 290 ----------- 28 files changed, 124 insertions(+), 1592 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts delete mode 100644 x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 1e0d798cf7f07..cf8c33d38f8fa 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -178,8 +178,6 @@ export interface HostResultList { request_page_size: number; /* the page index requested */ request_page_index: number; - /* the version of the query strategy */ - query_strategy_version: MetadataQueryStrategyVersions; /* policy IDs and versions */ policy_info?: HostInfo['policy_info']; } @@ -404,21 +402,11 @@ export enum HostStatus { INACTIVE = 'inactive', } -export enum MetadataQueryStrategyVersions { - VERSION_1 = 'v1', - VERSION_2 = 'v2', -} - export type PolicyInfo = Immutable<{ revision: number; id: string; }>; -export interface HostMetadataInfo { - metadata: HostMetadata; - query_strategy_version: MetadataQueryStrategyVersions; -} - export type HostInfo = Immutable<{ metadata: HostMetadata; host_status: HostStatus; @@ -438,8 +426,6 @@ export type HostInfo = Immutable<{ */ endpoint: PolicyInfo; }; - /* the version of the query strategy */ - query_strategy_version: MetadataQueryStrategyVersions; }>; // HostMetadataDetails is now just HostMetadata diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts index d6b24fa3cbdfc..76de52222bbd3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts @@ -15,7 +15,6 @@ import { HostPolicyResponse, HostResultList, HostStatus, - MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { @@ -54,7 +53,6 @@ export const endpointMetadataHttpMocks = httpHandlerMockFactory => { agentsWithEndpointsTotalError: undefined, endpointsTotal: 0, endpointsTotalError: undefined, - queryStrategyVersion: undefined, policyVersionInfo: undefined, hostStatus: undefined, isolationRequestState: createUninitialisedResourceState(), diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index f233fbdec5415..922f10cee2f8b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -28,7 +28,6 @@ import { nonExistingPolicies, patterns, searchBarQuery, - isTransformEnabled, getIsIsolationRequestPending, getCurrentIsolationRequestState, getActivityLogData, @@ -180,7 +179,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory HostResultList = (options = {}) => { const { total = 1, request_page_size: requestPageSize = 10, request_page_index: requestPageIndex = 0, - query_strategy_version: queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, } = options; // Skip any that are before the page we're on @@ -58,7 +55,6 @@ export const mockEndpointResultList: (options?: { hosts.push({ metadata: generator.generateHostMetadata(), host_status: HostStatus.UNHEALTHY, - query_strategy_version: queryStrategyVersion, }); } const mock: HostResultList = { @@ -66,7 +62,6 @@ export const mockEndpointResultList: (options?: { total, request_page_size: requestPageSize, request_page_index: requestPageIndex, - query_strategy_version: queryStrategyVersion, }; return mock; }; @@ -78,7 +73,6 @@ export const mockEndpointDetailsApiResult = (): HostInfo => { return { metadata: generator.generateHostMetadata(), host_status: HostStatus.UNHEALTHY, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_2, }; }; @@ -92,7 +86,6 @@ const endpointListApiPathHandlerMocks = ({ endpointPackagePolicies = [], policyResponse = generator.generatePolicyResponse(), agentPolicy = generator.generateAgentPolicy(), - queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, totalAgentsUsingEndpoint = 0, }: { /** route handlers will be setup for each individual host in this array */ @@ -101,7 +94,6 @@ const endpointListApiPathHandlerMocks = ({ endpointPackagePolicies?: GetPolicyListResponse['items']; policyResponse?: HostPolicyResponse; agentPolicy?: GetAgentPoliciesResponseItem; - queryStrategyVersion?: MetadataQueryStrategyVersions; totalAgentsUsingEndpoint?: number; } = {}) => { const apiHandlers = { @@ -119,7 +111,6 @@ const endpointListApiPathHandlerMocks = ({ request_page_size: 10, request_page_index: 0, total: endpointsResults?.length || 0, - query_strategy_version: queryStrategyVersion, }; }, @@ -192,16 +183,11 @@ export const setEndpointListApiMockImplementation: ( apiResponses?: Parameters[0] ) => void = ( mockedHttpService, - { - endpointsResults = mockEndpointResultList({ total: 3 }).hosts, - queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, - ...pathHandlersOptions - } = {} + { endpointsResults = mockEndpointResultList({ total: 3 }).hosts, ...pathHandlersOptions } = {} ) => { const apiHandlers = endpointListApiPathHandlerMocks({ ...pathHandlersOptions, endpointsResults, - queryStrategyVersion, }); mockedHttpService.post diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 1498ce08db8ab..c6bf13a3b5715 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -89,7 +89,6 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta total, request_page_size: pageSize, request_page_index: pageIndex, - query_strategy_version: queryStrategyVersion, policy_info: policyVersionInfo, } = action.payload; return { @@ -98,7 +97,6 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta total, pageSize, pageIndex, - queryStrategyVersion, policyVersionInfo, loading: false, error: undefined, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index 5771fbac957d8..4287cf9a109ea 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -15,7 +15,6 @@ import { HostPolicyResponseAppliedAction, HostPolicyResponseConfiguration, HostPolicyResponseActionStatus, - MetadataQueryStrategyVersions, HostStatus, ActivityLog, HostMetadata, @@ -90,17 +89,11 @@ export const agentsWithEndpointsTotalError = (state: Immutable) = state.agentsWithEndpointsTotalError; export const endpointsTotalError = (state: Immutable) => state.endpointsTotalError; -const queryStrategyVersion = (state: Immutable) => state.queryStrategyVersion; export const endpointPackageVersion = createSelector(endpointPackageInfo, (info) => isLoadedResourceState(info) ? info.data.version : undefined ); -export const isTransformEnabled = createSelector( - queryStrategyVersion, - (version) => version !== MetadataQueryStrategyVersions.VERSION_1 -); - /** * Returns the index patterns for the SearchBar to use for autosuggest */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 144cc7a64d6cb..875841cb55b73 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -13,7 +13,6 @@ import { HostPolicyResponse, AppLocation, PolicyData, - MetadataQueryStrategyVersions, HostStatus, HostIsolationResponse, EndpointPendingActions, @@ -96,8 +95,6 @@ export interface EndpointState { endpointsTotal: number; /** api error for total, actual Endpoints */ endpointsTotalError?: ServerApiError; - /** The query strategy version that informs whether the transform for KQL is enabled or not */ - queryStrategyVersion?: MetadataQueryStrategyVersions; /** The policy IDs and revision number of the corresponding agent, and endpoint. May be more recent than what's running */ policyVersionInfo?: HostInfo['policy_info']; /** The status of the host, which is mapped to the Elastic Agent status in Fleet */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index aafac38accd89..26d0d53e39982 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -23,7 +23,6 @@ import { HostPolicyResponseActionStatus, HostPolicyResponseAppliedAction, HostStatus, - MetadataQueryStrategyVersions, } from '../../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; import { POLICY_STATUS_TO_TEXT } from './host_constants'; @@ -167,31 +166,6 @@ describe('when on the endpoint list page', () => { }); }); - describe('when loading data with the query_strategy_version is `v1`', () => { - beforeEach(() => { - reactTestingLibrary.act(() => { - const mockedEndpointListData = mockEndpointResultList({ - total: 4, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_1, - }); - setEndpointListApiMockImplementation(coreStart.http, { - endpointsResults: mockedEndpointListData.hosts, - queryStrategyVersion: mockedEndpointListData.query_strategy_version, - }); - }); - }); - afterEach(() => { - jest.clearAllMocks(); - }); - it('should not display the KQL bar', async () => { - const renderResult = render(); - await reactTestingLibrary.act(async () => { - await middlewareSpy.waitForAction('serverReturnedEndpointList'); - }); - expect(renderResult.queryByTestId('adminSearchBar')).toBeNull(); - }); - }); - describe('when determining when to show the enrolling message', () => { afterEach(() => { jest.clearAllMocks(); @@ -268,7 +242,6 @@ describe('when on the endpoint list page', () => { reactTestingLibrary.act(() => { const mockedEndpointData = mockEndpointResultList({ total: 5 }); const hostListData = mockedEndpointData.hosts; - const queryStrategyVersion = mockedEndpointData.query_strategy_version; firstPolicyID = hostListData[0].metadata.Endpoint.policy.applied.id; firstPolicyRev = hostListData[0].metadata.Endpoint.policy.applied.endpoint_policy_version; @@ -329,7 +302,6 @@ describe('when on the endpoint list page', () => { hostListData[index].metadata.Endpoint.policy.applied, setup.policy ), - query_strategy_version: queryStrategyVersion, }; }); hostListData.forEach((item, index) => { @@ -535,8 +507,6 @@ describe('when on the endpoint list page', () => { // eslint-disable-next-line @typescript-eslint/naming-convention host_status, metadata: { agent, Endpoint, ...details }, - // eslint-disable-next-line @typescript-eslint/naming-convention - query_strategy_version, } = mockEndpointDetailsApiResult(); hostDetails = { @@ -555,7 +525,6 @@ describe('when on the endpoint list page', () => { id: '1', }, }, - query_strategy_version, }; const policy = docGenerator.generatePolicyPackagePolicy(); @@ -1198,7 +1167,7 @@ describe('when on the endpoint list page', () => { let renderResult: ReturnType; const mockEndpointListApi = () => { - const { hosts, query_strategy_version: queryStrategyVersion } = mockEndpointResultList(); + const { hosts } = mockEndpointResultList(); hostInfo = { host_status: hosts[0].host_status, metadata: { @@ -1222,7 +1191,6 @@ describe('when on the endpoint list page', () => { version: '7.14.0', }, }, - query_strategy_version: queryStrategyVersion, }; const packagePolicy = docGenerator.generatePolicyPackagePolicy(); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 0ee345431055b..c78d4ca6af634 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -120,7 +120,6 @@ export const EndpointList = () => { areEndpointsEnrolling, agentsWithEndpointsTotalError, endpointsTotalError, - isTransformEnabled, } = useEndpointSelector(selector); const { search } = useFormatUrl(SecurityPageName.administration); const { getAppUrl } = useAppUrl(); @@ -476,8 +475,8 @@ export const EndpointList = () => { const hasListData = listData && listData.length > 0; const refreshStyle = useMemo(() => { - return { display: endpointsExist && isTransformEnabled ? 'flex' : 'none', maxWidth: 200 }; - }, [endpointsExist, isTransformEnabled]); + return { display: endpointsExist ? 'flex' : 'none', maxWidth: 200 }; + }, [endpointsExist]); const refreshIsPaused = useMemo(() => { return !endpointsExist ? false : hasSelectedEndpoint ? true : !isAutoRefreshEnabled; @@ -492,8 +491,8 @@ export const EndpointList = () => { }, [endpointsTotalError, agentsWithEndpointsTotalError]); const shouldShowKQLBar = useMemo(() => { - return endpointsExist && !patternsError && isTransformEnabled; - }, [endpointsExist, patternsError, isTransformEnabled]); + return endpointsExist && !patternsError; + }, [endpointsExist, patternsError]); return ( ; -} - -export const createMetadataService = (packageService: PackageService): MetadataService => { - return { - async queryStrategy( - savedObjectsClient: SavedObjectsClientContract, - version?: MetadataQueryStrategyVersions - ): Promise { - if (version === MetadataQueryStrategyVersions.VERSION_1) { - return metadataQueryStrategyV1(); - } - if (!packageService) { - throw new Error('package service is uninitialized'); - } - - if (version === MetadataQueryStrategyVersions.VERSION_2 || !version) { - const assets = - (await packageService.getInstallation({ savedObjectsClient, pkgName: 'endpoint' })) - ?.installed_es ?? []; - const expectedTransformAssets = assets.filter( - (ref) => - ref.type === ElasticsearchAssetType.transform && - ref.id.startsWith(metadataTransformPrefix) - ); - if (expectedTransformAssets && expectedTransformAssets.length === 1) { - return metadataQueryStrategyV2(); - } - return metadataQueryStrategyV1(); - } - return metadataQueryStrategyV1(); - }, - }; -}; - export type EndpointAppContextServiceStartContract = Partial< Pick< FleetStartContract, @@ -114,7 +66,6 @@ export class EndpointAppContextService { private packagePolicyService: PackagePolicyServiceInterface | undefined; private agentPolicyService: AgentPolicyServiceInterface | undefined; private savedObjectsStart: SavedObjectsServiceStart | undefined; - private metadataService: MetadataService | undefined; private config: ConfigType | undefined; private license: LicenseService | undefined; public security: SecurityPluginStart | undefined; @@ -128,7 +79,6 @@ export class EndpointAppContextService { this.agentPolicyService = dependencies.agentPolicyService; this.manifestManager = dependencies.manifestManager; this.savedObjectsStart = dependencies.savedObjectsStart; - this.metadataService = createMetadataService(dependencies.packageService!); this.config = dependencies.config; this.license = dependencies.licenseService; this.security = dependencies.security; @@ -176,10 +126,6 @@ export class EndpointAppContextService { return this.agentPolicyService; } - public getMetadataService(): MetadataService | undefined { - return this.metadataService; - } - public getManifestManager(): ManifestManager | undefined { return this.manifestManager; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts index 45063ca92e2b0..fceb45b17c258 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts @@ -83,7 +83,7 @@ export const isolationRequestHandler = function ( // fetch the Agent IDs to send the commands to const endpointIDs = [...new Set(req.body.endpoint_ids)]; // dedupe - const endpointData = await getMetadataForEndpoints(endpointIDs, context, endpointContext); + const endpointData = await getMetadataForEndpoints(endpointIDs, context); const casesClient = await endpointContext.service.getCasesClient(req); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts index 960f3abda8195..39aa0bf2d8cf7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { HostStatus, MetadataQueryStrategyVersions } from '../../../../common/endpoint/types'; +import { HostStatus } from '../../../../common/endpoint/types'; import { createMockMetadataRequestContext } from '../../mocks'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { enrichHostMetadata, MetadataRequestContext } from './handlers'; @@ -18,30 +18,6 @@ describe('test document enrichment', () => { metaReqCtx = createMockMetadataRequestContext(); }); - // verify query version passed through - describe('metadata query strategy enrichment', () => { - it('should match v1 strategy when directed', async () => { - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_1 - ); - expect(enrichedHostList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - it('should match v2 strategy when directed', async () => { - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); - expect(enrichedHostList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); - }); - }); - describe('host status enrichment', () => { let statusFn: jest.Mock; @@ -57,77 +33,49 @@ describe('test document enrichment', () => { it('should return host healthy for online agent', async () => { statusFn.mockImplementation(() => 'online'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.HEALTHY); }); it('should return host offline for offline agent', async () => { statusFn.mockImplementation(() => 'offline'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.OFFLINE); }); it('should return host updating for unenrolling agent', async () => { statusFn.mockImplementation(() => 'unenrolling'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UPDATING); }); it('should return host unhealthy for degraded agent', async () => { statusFn.mockImplementation(() => 'degraded'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); it('should return host unhealthy for erroring agent', async () => { statusFn.mockImplementation(() => 'error'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); it('should return host unhealthy for warning agent', async () => { statusFn.mockImplementation(() => 'warning'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); it('should return host unhealthy for invalid agent', async () => { statusFn.mockImplementation(() => 'asliduasofb'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); }); @@ -164,11 +112,7 @@ describe('test document enrichment', () => { }; }); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.policy_info).toBeDefined(); expect(enrichedHostList.policy_info!.agent.applied.id).toEqual(policyID); expect(enrichedHostList.policy_info!.agent.applied.revision).toEqual(policyRev); @@ -184,11 +128,7 @@ describe('test document enrichment', () => { }; }); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.policy_info).toBeDefined(); expect(enrichedHostList.policy_info!.agent.configured.id).toEqual(policyID); expect(enrichedHostList.policy_info!.agent.configured.revision).toEqual(policyRev); @@ -209,11 +149,7 @@ describe('test document enrichment', () => { }; }); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.policy_info).toBeDefined(); expect(enrichedHostList.policy_info!.endpoint.id).toEqual(policyID); expect(enrichedHostList.policy_info!.endpoint.revision).toEqual(policyRev); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 815f30e6e7426..2ceca170881e3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -17,10 +17,8 @@ import { import { HostInfo, HostMetadata, - HostMetadataInfo, HostResultList, HostStatus, - MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import type { SecuritySolutionRequestHandlerContext } from '../../../types'; @@ -33,6 +31,10 @@ import { findAllUnenrolledAgentIds } from './support/unenroll'; import { findAgentIDsByStatus } from './support/agent_status'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { fleetAgentStatusToEndpointHostStatus } from '../../utils'; +import { + queryResponseToHostListResult, + queryResponseToHostResult, +} from './support/query_strategies'; export interface MetadataRequestContext { esClient?: IScopedClusterClient; @@ -58,8 +60,7 @@ export const getLogger = (endpointAppContext: EndpointAppContext): Logger => { export const getMetadataListRequestHandler = function ( endpointAppContext: EndpointAppContext, - logger: Logger, - queryStrategyVersion?: MetadataQueryStrategyVersions + logger: Logger ): RequestHandler< unknown, unknown, @@ -96,24 +97,15 @@ export const getMetadataListRequestHandler = function ( ) : undefined; - const queryStrategy = await endpointAppContext.service - ?.getMetadataService() - ?.queryStrategy(context.core.savedObjects.client, queryStrategyVersion); - - const queryParams = await kibanaRequestToMetadataListESQuery( - request, - endpointAppContext, - queryStrategy!, - { - unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), - statusAgentIDs: statusIDs, - } - ); + const queryParams = await kibanaRequestToMetadataListESQuery(request, endpointAppContext, { + unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), + statusAgentIDs: statusIDs, + }); const result = await context.core.elasticsearch.client.asCurrentUser.search( queryParams ); - const hostListQueryResult = queryStrategy!.queryResponseToHostListResult(result.body); + const hostListQueryResult = queryResponseToHostListResult(result.body); return response.ok({ body: await mapToHostResultList(queryParams, hostListQueryResult, metadataRequestContext), }); @@ -122,8 +114,7 @@ export const getMetadataListRequestHandler = function ( export const getMetadataRequestHandler = function ( endpointAppContext: EndpointAppContext, - logger: Logger, - queryStrategyVersion?: MetadataQueryStrategyVersions + logger: Logger ): RequestHandler< TypeOf, unknown, @@ -145,11 +136,7 @@ export const getMetadataRequestHandler = function ( }; try { - const doc = await getHostData( - metadataRequestContext, - request?.params?.id, - queryStrategyVersion - ); + const doc = await getHostData(metadataRequestContext, request?.params?.id); if (doc) { return response.ok({ body: doc }); } @@ -169,9 +156,8 @@ export const getMetadataRequestHandler = function ( export async function getHostMetaData( metadataRequestContext: MetadataRequestContext, - id: string, - queryStrategyVersion?: MetadataQueryStrategyVersions -): Promise { + id: string +): Promise { if ( !metadataRequestContext.esClient && !metadataRequestContext.requestHandlerContext?.core.elasticsearch.client @@ -190,32 +176,23 @@ export async function getHostMetaData( metadataRequestContext.requestHandlerContext?.core.elasticsearch .client) as IScopedClusterClient; - const esSavedObjectClient = - metadataRequestContext?.savedObjectsClient ?? - (metadataRequestContext.requestHandlerContext?.core.savedObjects - .client as SavedObjectsClientContract); - - const queryStrategy = await metadataRequestContext.endpointAppContextService - ?.getMetadataService() - ?.queryStrategy(esSavedObjectClient, queryStrategyVersion); - const query = getESQueryHostMetadataByID(id, queryStrategy!); + const query = getESQueryHostMetadataByID(id); const response = await esClient.asCurrentUser.search(query); - const hostResult = queryStrategy!.queryResponseToHostResult(response.body); + const hostResult = queryResponseToHostResult(response.body); const hostMetadata = hostResult.result; if (!hostMetadata) { return undefined; } - return { metadata: hostMetadata, query_strategy_version: hostResult.queryStrategyVersion }; + return hostMetadata; } export async function getHostData( metadataRequestContext: MetadataRequestContext, - id: string, - queryStrategyVersion?: MetadataQueryStrategyVersions + id: string ): Promise { if (!metadataRequestContext.savedObjectsClient) { throw Boom.badRequest('savedObjectsClient not found'); @@ -228,25 +205,21 @@ export async function getHostData( throw Boom.badRequest('esClient not found'); } - const hostResult = await getHostMetaData(metadataRequestContext, id, queryStrategyVersion); + const hostMetadata = await getHostMetaData(metadataRequestContext, id); - if (!hostResult) { + if (!hostMetadata) { return undefined; } - const agent = await findAgent(metadataRequestContext, hostResult.metadata); + const agent = await findAgent(metadataRequestContext, hostMetadata); if (agent && !agent.active) { throw Boom.badRequest('the requested endpoint is unenrolled'); } - const metadata = await enrichHostMetadata( - hostResult.metadata, - metadataRequestContext, - hostResult.query_strategy_version - ); + const metadata = await enrichHostMetadata(hostMetadata, metadataRequestContext); - return { ...metadata, query_strategy_version: hostResult.query_strategy_version }; + return metadata; } async function findAgent( @@ -293,15 +266,10 @@ export async function mapToHostResultList( request_page_index: queryParams.from, hosts: await Promise.all( hostListQueryResult.resultList.map(async (entry) => - enrichHostMetadata( - entry, - metadataRequestContext, - hostListQueryResult.queryStrategyVersion - ) + enrichHostMetadata(entry, metadataRequestContext) ) ), total: totalNumberOfHosts, - query_strategy_version: hostListQueryResult.queryStrategyVersion, }; } else { return { @@ -309,15 +277,13 @@ export async function mapToHostResultList( request_page_index: queryParams.from, total: totalNumberOfHosts, hosts: [], - query_strategy_version: hostListQueryResult.queryStrategyVersion, }; } } export async function enrichHostMetadata( hostMetadata: HostMetadata, - metadataRequestContext: MetadataRequestContext, - metadataQueryStrategyVersion: MetadataQueryStrategyVersions + metadataRequestContext: MetadataRequestContext ): Promise { let hostStatus = HostStatus.UNHEALTHY; let elasticAgentId = hostMetadata?.elastic?.agent?.id; @@ -413,6 +379,5 @@ export async function enrichHostMetadata( metadata: hostMetadata, host_status: hostStatus, policy_info: policyInfo, - query_strategy_version: metadataQueryStrategyVersion, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts index b4784c1ff5ed4..d9c3e6c195307 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts @@ -7,19 +7,15 @@ import { schema } from '@kbn/config-schema'; -import { HostStatus, MetadataQueryStrategyVersions } from '../../../../common/endpoint/types'; +import { HostStatus } from '../../../../common/endpoint/types'; import { EndpointAppContext } from '../../types'; import { getLogger, getMetadataListRequestHandler, getMetadataRequestHandler } from './handlers'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { - BASE_ENDPOINT_ROUTE, HOST_METADATA_GET_ROUTE, HOST_METADATA_LIST_ROUTE, } from '../../../../common/endpoint/constants'; -export const METADATA_REQUEST_V1_ROUTE = `${BASE_ENDPOINT_ROUTE}/v1/metadata`; -export const GET_METADATA_REQUEST_V1_ROUTE = `${METADATA_REQUEST_V1_ROUTE}/{id}`; - /* Filters that can be applied to the endpoint fetch route */ export const endpointFilters = schema.object({ kql: schema.nullable(schema.string()), @@ -69,18 +65,6 @@ export function registerEndpointRoutes( endpointAppContext: EndpointAppContext ) { const logger = getLogger(endpointAppContext); - router.post( - { - path: `${METADATA_REQUEST_V1_ROUTE}`, - validate: GetMetadataListRequestSchema, - options: { authRequired: true, tags: ['access:securitySolution'] }, - }, - getMetadataListRequestHandler( - endpointAppContext, - logger, - MetadataQueryStrategyVersions.VERSION_1 - ) - ); router.post( { @@ -91,15 +75,6 @@ export function registerEndpointRoutes( getMetadataListRequestHandler(endpointAppContext, logger) ); - router.get( - { - path: `${GET_METADATA_REQUEST_V1_ROUTE}`, - validate: GetMetadataRequestSchema, - options: { authRequired: true, tags: ['access:securitySolution'] }, - }, - getMetadataRequestHandler(endpointAppContext, logger, MetadataQueryStrategyVersions.VERSION_1) - ); - router.get( { path: `${HOST_METADATA_GET_ROUTE}`, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 5250f7c49d6ad..1e56f79aa0b32 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -19,12 +19,7 @@ import { loggingSystemMock, savedObjectsClientMock, } from '../../../../../../../src/core/server/mocks'; -import { - HostInfo, - HostResultList, - HostStatus, - MetadataQueryStrategyVersions, -} from '../../../../common/endpoint/types'; +import { HostInfo, HostResultList, HostStatus } from '../../../../common/endpoint/types'; import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; import { registerEndpointRoutes } from './index'; import { @@ -39,7 +34,7 @@ import { import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { Agent, ElasticsearchAssetType } from '../../../../../fleet/common/types/models'; -import { createV1SearchResponse, createV2SearchResponse } from './support/test_support'; +import { createV2SearchResponse } from './support/test_support'; import { PackageService } from '../../../../../fleet/server/services'; import { HOST_METADATA_LIST_ROUTE, @@ -98,94 +93,6 @@ describe('test endpoint route', () => { ); }); - describe('with no transform package', () => { - beforeEach(() => { - endpointAppContextService = new EndpointAppContextService(); - mockPackageService = createMockPackageService(); - mockPackageService.getInstallation.mockReturnValue(Promise.resolve(undefined)); - endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); - mockAgentService = startContract.agentService!; - - registerEndpointRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }); - }); - - afterEach(() => endpointAppContextService.stop()); - - it('test find the latest of all endpoints', async () => { - const mockRequest = httpServerMock.createKibanaRequest({}); - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${HOST_METADATA_LIST_ROUTE}`) - )!; - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(0); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - it('should return a single endpoint with status healthy', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('online'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${HOST_METADATA_LIST_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result).toHaveProperty('metadata.Endpoint'); - expect(result.host_status).toEqual(HostStatus.HEALTHY); - expect(result.query_strategy_version).toEqual(MetadataQueryStrategyVersions.VERSION_1); - }); - }); - describe('with new transform package', () => { beforeEach(() => { endpointAppContextService = new EndpointAppContextService(); @@ -254,9 +161,6 @@ describe('test endpoint route', () => { expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(0); expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); }); it('test find the latest of all endpoints with paging properties', async () => { @@ -311,9 +215,6 @@ describe('test endpoint route', () => { expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); }); it('test find the latest of all endpoints with paging and filters properties', async () => { @@ -405,9 +306,6 @@ describe('test endpoint route', () => { expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); }); describe('Endpoint Details route', () => { @@ -475,7 +373,6 @@ describe('test endpoint route', () => { const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; expect(result).toHaveProperty('metadata.Endpoint'); expect(result.host_status).toEqual(HostStatus.HEALTHY); - expect(result.query_strategy_version).toEqual(MetadataQueryStrategyVersions.VERSION_2); }); it('should return a single endpoint with status unhealthy when AgentService throw 404', async () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts deleted file mode 100644 index 29b2c231cc4a5..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts +++ /dev/null @@ -1,456 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - KibanaResponseFactory, - RequestHandler, - RouteConfig, - SavedObjectsClientContract, - SavedObjectsErrorHelpers, -} from '../../../../../../../src/core/server'; -import { - ClusterClientMock, - ScopedClusterClientMock, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../../src/core/server/elasticsearch/client/mocks'; -import { - elasticsearchServiceMock, - httpServerMock, - httpServiceMock, - loggingSystemMock, - savedObjectsClientMock, -} from '../../../../../../../src/core/server/mocks'; -import { - HostInfo, - HostResultList, - HostStatus, - MetadataQueryStrategyVersions, -} from '../../../../common/endpoint/types'; -import { registerEndpointRoutes, METADATA_REQUEST_V1_ROUTE } from './index'; -import { - createMockEndpointAppContextServiceStartContract, - createMockPackageService, - createRouteHandlerContext, -} from '../../mocks'; -import { - EndpointAppContextService, - EndpointAppContextServiceStartContract, -} from '../../endpoint_app_context_services'; -import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; -import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; -import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; -import { Agent } from '../../../../../fleet/common/types/models'; -import { createV1SearchResponse } from './support/test_support'; -import { PackageService } from '../../../../../fleet/server/services'; -import type { SecuritySolutionPluginRouter } from '../../../types'; -import { PackagePolicyServiceInterface } from '../../../../../fleet/server'; - -describe('test endpoint route v1', () => { - let routerMock: jest.Mocked; - let mockResponse: jest.Mocked; - let mockClusterClient: ClusterClientMock; - let mockScopedClient: ScopedClusterClientMock; - let mockSavedObjectClient: jest.Mocked; - let mockPackageService: jest.Mocked; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let routeHandler: RequestHandler; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let routeConfig: RouteConfig; - // tests assume that fleet is enabled, and thus agentService is available - let mockAgentService: Required< - ReturnType - >['agentService']; - let endpointAppContextService: EndpointAppContextService; - let startContract: EndpointAppContextServiceStartContract; - const noUnenrolledAgent = { - agents: [], - total: 0, - page: 1, - perPage: 1, - }; - - beforeEach(() => { - mockClusterClient = elasticsearchServiceMock.createClusterClient(); - mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); - mockSavedObjectClient = savedObjectsClientMock.create(); - mockClusterClient.asScoped.mockReturnValue(mockScopedClient); - routerMock = httpServiceMock.createRouter(); - mockResponse = httpServerMock.createResponseFactory(); - endpointAppContextService = new EndpointAppContextService(); - mockPackageService = createMockPackageService(); - mockPackageService.getInstallation.mockReturnValue(Promise.resolve(undefined)); - startContract = createMockEndpointAppContextServiceStartContract(); - endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); - mockAgentService = startContract.agentService!; - - (startContract.packagePolicyService as jest.Mocked).list.mockImplementation( - () => { - return Promise.resolve({ - items: [], - total: 0, - page: 1, - perPage: 1000, - }); - } - ); - - registerEndpointRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }); - }); - - afterEach(() => endpointAppContextService.stop()); - - it('test find the latest of all endpoints', async () => { - const mockRequest = httpServerMock.createKibanaRequest({}); - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(0); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - it('test find the latest of all endpoints with paging properties', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 1, - }, - ], - }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ - body: createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()), - }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect( - (mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool - .must_not - ).toContainEqual({ - terms: { - 'elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }); - expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(10); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - it('test find the latest of all endpoints with paging and filters properties', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 1, - }, - ], - - filters: { kql: 'not host.ip:10.140.73.246' }, - }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ - body: createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()), - }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toBeCalled(); - // needs to have the KQL filter passed through - expect( - (mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must - ).toContainEqual({ - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, - }, - }); - // and unenrolled should be filtered out. - expect( - (mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must - ).toContainEqual({ - bool: { - must_not: [ - { - terms: { - 'elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }, - { - terms: { - // we actually don't care about HostDetails in v1 queries, but - // harder to set up the expectation to ignore its inclusion succinctly - 'HostDetails.elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }, - ], - }, - }); - expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(10); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - describe('Endpoint Details route', () => { - it('should return 404 on no results', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } }); - - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: createV1SearchResponse() }) - ); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.notFound).toBeCalled(); - const message = mockResponse.notFound.mock.calls[0][0]?.body; - expect(message).toEqual('Endpoint Not Found'); - }); - - it('should return a single endpoint with status healthy', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('online'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result).toHaveProperty('metadata.Endpoint'); - expect(result.host_status).toEqual(HostStatus.HEALTHY); - }); - - it('should return a single endpoint with status unhealthy when AgentService throw 404', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockImplementation(() => { - SavedObjectsErrorHelpers.createGenericNotFoundError(); - }); - - mockAgentService.getAgent = jest.fn().mockImplementation(() => { - SavedObjectsErrorHelpers.createGenericNotFoundError(); - }); - - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result.host_status).toEqual(HostStatus.UNHEALTHY); - }); - - it('should return a single endpoint with status unhealthy when status is not offline, online or enrolling', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('warning'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result.host_status).toEqual(HostStatus.UNHEALTHY); - }); - - it('should throw error when endpoint agent is not active', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: false, - } as unknown) as Agent); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(mockResponse.customError).toBeCalled(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts index e790c1de1a5b8..87de5a540ea99 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts @@ -11,38 +11,29 @@ import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { metadataCurrentIndexPattern } from '../../../../common/endpoint/constants'; import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; -import { metadataQueryStrategyV2 } from './support/query_strategies'; import { get } from 'lodash'; describe('query builder', () => { describe('MetadataListESQuery', () => { it('queries the correct index', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.index).toEqual(metadataCurrentIndexPattern); }); it('sorts using *event.created', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.body.sort).toContainEqual({ 'event.created': { order: 'desc', @@ -61,16 +52,12 @@ describe('query builder', () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {}, }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.body.query).toHaveProperty('match_all'); }); @@ -87,7 +74,6 @@ describe('query builder', () => { config: () => Promise.resolve(createMockConfig()), experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), }, - metadataQueryStrategyV2(), { unenrolledAgentIds: [unenrolledElasticAgentId], } @@ -111,16 +97,12 @@ describe('query builder', () => { filters: { kql: 'not host.ip:10.140.73.246' }, }, }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.body.query.bool.must).toContainEqual({ bool: { @@ -160,7 +142,6 @@ describe('query builder', () => { createMockConfig().enableExperimental ), }, - metadataQueryStrategyV2(), { unenrolledAgentIds: [unenrolledElasticAgentId], } @@ -197,13 +178,13 @@ describe('query builder', () => { describe('MetadataGetQuery', () => { it('searches the correct index', () => { - const query = getESQueryHostMetadataByID('nonsense-id', metadataQueryStrategyV2()); + const query = getESQueryHostMetadataByID('nonsense-id'); expect(query.index).toEqual(metadataCurrentIndexPattern); }); it('searches for the correct ID', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; - const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2()); + const query = getESQueryHostMetadataByID(mockID); expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({ term: { 'agent.id': mockID }, @@ -212,7 +193,7 @@ describe('query builder', () => { it('supports HostDetails in schema for backwards compat', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; - const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2()); + const query = getESQueryHostMetadataByID(mockID); expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({ term: { 'HostDetails.agent.id': mockID }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts index f0950e5fb79ba..99ec1d1022747 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts @@ -6,9 +6,10 @@ */ import type { estypes } from '@elastic/elasticsearch'; +import { metadataCurrentIndexPattern } from '../../../../common/endpoint/constants'; import { KibanaRequest } from '../../../../../../../src/core/server'; import { esKuery } from '../../../../../../../src/plugins/data/server'; -import { EndpointAppContext, MetadataQueryStrategy } from '../../types'; +import { EndpointAppContext } from '../../types'; export interface QueryBuilderOptions { unenrolledAgentIds?: string[]; @@ -39,7 +40,6 @@ export async function kibanaRequestToMetadataListESQuery( // eslint-disable-next-line @typescript-eslint/no-explicit-any request: KibanaRequest, endpointAppContext: EndpointAppContext, - metadataQueryStrategy: MetadataQueryStrategy, queryBuilderOptions?: QueryBuilderOptions // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise> { @@ -49,16 +49,15 @@ export async function kibanaRequestToMetadataListESQuery( body: { query: buildQueryBody( request, - metadataQueryStrategy, queryBuilderOptions?.unenrolledAgentIds!, queryBuilderOptions?.statusAgentIDs! ), - ...metadataQueryStrategy.extraBodyProperties, + track_total_hits: true, sort: MetadataSortMethod, }, from: pagingProperties.pageIndex * pagingProperties.pageSize, size: pagingProperties.pageSize, - index: metadataQueryStrategy.index, + index: metadataCurrentIndexPattern, }; } @@ -86,7 +85,6 @@ async function getPagingProperties( function buildQueryBody( // eslint-disable-next-line @typescript-eslint/no-explicit-any request: KibanaRequest, - metadataQueryStrategy: MetadataQueryStrategy, unerolledAgentIds: string[] | undefined, statusAgentIDs: string[] | undefined // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -144,10 +142,7 @@ function buildQueryBody( }; } -export function getESQueryHostMetadataByID( - agentID: string, - metadataQueryStrategy: MetadataQueryStrategy -): estypes.SearchRequest { +export function getESQueryHostMetadataByID(agentID: string): estypes.SearchRequest { return { body: { query: { @@ -167,14 +162,11 @@ export function getESQueryHostMetadataByID( sort: MetadataSortMethod, size: 1, }, - index: metadataQueryStrategy.index, + index: metadataCurrentIndexPattern, }; } -export function getESQueryHostMetadataByIDs( - agentIDs: string[], - metadataQueryStrategy: MetadataQueryStrategy -) { +export function getESQueryHostMetadataByIDs(agentIDs: string[]) { return { body: { query: { @@ -193,6 +185,6 @@ export function getESQueryHostMetadataByIDs( }, sort: MetadataSortMethod, }, - index: metadataQueryStrategy.index, + index: metadataCurrentIndexPattern, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts deleted file mode 100644 index c18c585cd3d34..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts +++ /dev/null @@ -1,188 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { httpServerMock, loggingSystemMock } from '../../../../../../../src/core/server/mocks'; -import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; -import { EndpointAppContextService } from '../../endpoint_app_context_services'; -import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; -import { metadataIndexPattern } from '../../../../common/endpoint/constants'; -import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; -import { metadataQueryStrategyV1 } from './support/query_strategies'; -import { get } from 'lodash'; - -describe('query builder v1', () => { - describe('MetadataListESQuery', () => { - it('test default query params for all endpoints metadata when no params or body is provided', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: {}, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV1() - ); - - expect(query.body.query).toHaveProperty('match_all'); // no filtering - expect(query.body.collapse).toEqual({ - field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, - }); - expect(query.body.aggs).toEqual({ - total: { - cardinality: { - field: 'agent.id', - }, - }, - }); - expect(query.index).toEqual(metadataIndexPattern); - }); - - it( - 'test default query params for all endpoints metadata when no params or body is provided ' + - 'with unenrolled host ids excluded', - async () => { - const unenrolledElasticAgentId = '1fdca33f-799f-49f4-939c-ea4383c77672'; - const mockRequest = httpServerMock.createKibanaRequest({ - body: {}, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue( - createMockConfig().enableExperimental - ), - }, - metadataQueryStrategyV1(), - { - unenrolledAgentIds: [unenrolledElasticAgentId], - } - ); - expect(Object.keys(query.body.query.bool)).toEqual(['must_not']); // only filtering out unenrolled - expect(query.body.query.bool.must_not).toContainEqual({ - terms: { 'elastic.agent.id': [unenrolledElasticAgentId] }, - }); - } - ); - }); - - describe('test query builder with kql filter', () => { - it('test default query params for all endpoints metadata when body filter is provided', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - filters: { kql: 'not host.ip:10.140.73.246' }, - }, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV1() - ); - expect(query.body.query.bool.must).toHaveLength(1); // should not be any other filtering happening - expect(query.body.query.bool.must).toContainEqual({ - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, - }, - }); - }); - - it( - 'test default query params for all endpoints endpoint metadata excluding unerolled endpoint ' + - 'and when body filter is provided', - async () => { - const unenrolledElasticAgentId = '1fdca33f-799f-49f4-939c-ea4383c77672'; - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - filters: { kql: 'not host.ip:10.140.73.246' }, - }, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue( - createMockConfig().enableExperimental - ), - }, - metadataQueryStrategyV1(), - { - unenrolledAgentIds: [unenrolledElasticAgentId], - } - ); - - expect(query.body.query.bool.must.length).toBeGreaterThan(1); - // unenrollment filter should be there - expect(query.body.query.bool.must).toContainEqual({ - bool: { - must_not: [ - { terms: { 'elastic.agent.id': [unenrolledElasticAgentId] } }, - // below is not actually necessary behavior for v1, but hard to structure the test to ignore it - { terms: { 'HostDetails.elastic.agent.id': [unenrolledElasticAgentId] } }, - ], - }, - }); - // and KQL should also be there - expect(query.body.query.bool.must).toContainEqual({ - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, - }, - }); - } - ); - }); - - describe('MetadataGetQuery', () => { - it('searches for the correct ID', () => { - const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; - const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV1()); - - expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({ - term: { 'agent.id': mockID }, - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts index 506c02fc2f1ec..2d7bff4a53f3f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts @@ -6,102 +6,39 @@ */ import { SearchResponse } from '@elastic/elasticsearch/api/types'; -import { - metadataCurrentIndexPattern, - metadataIndexPattern, -} from '../../../../../common/endpoint/constants'; -import { HostMetadata, MetadataQueryStrategyVersions } from '../../../../../common/endpoint/types'; -import { HostListQueryResult, HostQueryResult, MetadataQueryStrategy } from '../../../types'; +import { HostMetadata } from '../../../../../common/endpoint/types'; +import { HostListQueryResult, HostQueryResult } from '../../../types'; -export function metadataQueryStrategyV1(): MetadataQueryStrategy { - return { - index: metadataIndexPattern, - extraBodyProperties: { - collapse: { - field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, - }, - aggs: { - total: { - cardinality: { - field: 'agent.id', - }, - }, - }, - }, - queryResponseToHostListResult: ( - searchResponse: SearchResponse - ): HostListQueryResult => { - const response = searchResponse as SearchResponse; - return { - resultLength: - ((response?.aggregations?.total as unknown) as { value?: number; relation: string }) - ?.value || 0, - resultList: response.hits.hits - .map((hit) => hit.inner_hits?.most_recent.hits.hits) - .flatMap((data) => data) - .map((entry) => (entry?._source ?? {}) as HostMetadata), - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_1, - }; - }, - queryResponseToHostResult: (searchResponse: SearchResponse): HostQueryResult => { - const response = searchResponse as SearchResponse; - return { - resultLength: response.hits.hits.length, - result: response.hits.hits.length > 0 ? response.hits.hits[0]._source : undefined, - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_1, - }; - }, - }; +// remove the top-level 'HostDetails' property if found, from previous schemas +function stripHostDetails(host: HostMetadata | { HostDetails: HostMetadata }): HostMetadata { + return 'HostDetails' in host ? host.HostDetails : host; } -export function metadataQueryStrategyV2(): MetadataQueryStrategy { +export const queryResponseToHostResult = ( + searchResponse: SearchResponse +): HostQueryResult => { + const response = searchResponse as SearchResponse; return { - index: metadataCurrentIndexPattern, - extraBodyProperties: { - track_total_hits: true, - }, - queryResponseToHostListResult: ( - searchResponse: SearchResponse - ): HostListQueryResult => { - const response = searchResponse as SearchResponse< - HostMetadata | { HostDetails: HostMetadata } - >; - const list = - response.hits.hits.length > 0 - ? response.hits.hits.map((entry) => stripHostDetails(entry?._source as HostMetadata)) - : []; - - return { - resultLength: - ((response.hits?.total as unknown) as { value: number; relation: string }).value || 0, - resultList: list, - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_2, - }; - }, - queryResponseToHostResult: ( - searchResponse: SearchResponse - ): HostQueryResult => { - const response = searchResponse as SearchResponse< - HostMetadata | { HostDetails: HostMetadata } - >; - return { - resultLength: response.hits.hits.length, - result: - response.hits.hits.length > 0 - ? stripHostDetails(response.hits.hits[0]._source as HostMetadata) - : undefined, - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_2, - }; - }, + resultLength: response.hits.hits.length, + result: + response.hits.hits.length > 0 + ? stripHostDetails(response.hits.hits[0]._source as HostMetadata) + : undefined, }; -} +}; -// remove the top-level 'HostDetails' property if found, from previous schemas -function stripHostDetails(host: HostMetadata | { HostDetails: HostMetadata }): HostMetadata { - return 'HostDetails' in host ? host.HostDetails : host; -} +export const queryResponseToHostListResult = ( + searchResponse: SearchResponse +): HostListQueryResult => { + const response = searchResponse as SearchResponse; + const list = + response.hits.hits.length > 0 + ? response.hits.hits.map((entry) => stripHostDetails(entry?._source as HostMetadata)) + : []; + + return { + resultLength: + ((response.hits?.total as unknown) as { value: number; relation: string }).value || 0, + resultList: list, + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts index bc23c253c4347..a0530590f5f9f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts @@ -8,62 +8,6 @@ import { SearchResponse } from 'elasticsearch'; import { HostMetadata } from '../../../../../common/endpoint/types'; -export function createV1SearchResponse(hostMetadata?: HostMetadata): SearchResponse { - return ({ - took: 15, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 5, - relation: 'eq', - }, - max_score: null, - hits: hostMetadata - ? [ - { - _index: 'metrics-endpoint.metadata-default', - _id: '8FhM0HEBYyRTvb6lOQnw', - _score: null, - _source: hostMetadata, - sort: [1588337587997], - inner_hits: { - most_recent: { - hits: { - total: { - value: 2, - relation: 'eq', - }, - max_score: null, - hits: [ - { - _index: 'metrics-endpoint.metadata-default', - _id: 'W6Vo1G8BYQH1gtPUgYkC', - _score: null, - _source: hostMetadata, - sort: [1579816615336], - }, - ], - }, - }, - }, - }, - ] - : [], - }, - aggregations: { - total: { - value: 1, - }, - }, - } as unknown) as SearchResponse; -} - export function createV2SearchResponse(hostMetadata?: HostMetadata): SearchResponse { return ({ took: 15, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts index 0ca1983aa68d5..1a5515d8122f1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts @@ -10,20 +10,15 @@ import { SearchResponse } from 'elasticsearch'; import { HostMetadata } from '../../../common/endpoint/types'; import { SecuritySolutionRequestHandlerContext } from '../../types'; import { getESQueryHostMetadataByIDs } from '../routes/metadata/query_builders'; -import { EndpointAppContext } from '../types'; +import { queryResponseToHostListResult } from '../routes/metadata/support/query_strategies'; export async function getMetadataForEndpoints( endpointIDs: string[], - requestHandlerContext: SecuritySolutionRequestHandlerContext, - endpointAppContext: EndpointAppContext + requestHandlerContext: SecuritySolutionRequestHandlerContext ): Promise { - const queryStrategy = await endpointAppContext.service - ?.getMetadataService() - ?.queryStrategy(requestHandlerContext.core.savedObjects.client); - - const query = getESQueryHostMetadataByIDs(endpointIDs, queryStrategy!); + const query = getESQueryHostMetadataByIDs(endpointIDs); const esClient = requestHandlerContext.core.elasticsearch.client.asCurrentUser; const { body } = await esClient.search(query as SearchRequest); - const hosts = queryStrategy!.queryResponseToHostListResult(body as SearchResponse); + const hosts = queryResponseToHostListResult(body as SearchResponse); return hosts.resultList; } diff --git a/x-pack/plugins/security_solution/server/endpoint/types.ts b/x-pack/plugins/security_solution/server/endpoint/types.ts index 6076aa9af635b..bc52b759b9f0a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/types.ts @@ -7,11 +7,9 @@ import { LoggerFactory } from 'kibana/server'; -import { SearchResponse } from '@elastic/elasticsearch/api/types'; -import { JsonObject } from '@kbn/common-utils'; import { ConfigType } from '../config'; import { EndpointAppContextService } from './endpoint_app_context_services'; -import { HostMetadata, MetadataQueryStrategyVersions } from '../../common/endpoint/types'; +import { HostMetadata } from '../../common/endpoint/types'; import { ExperimentalFeatures } from '../../common/experimental_features'; /** @@ -31,20 +29,9 @@ export interface EndpointAppContext { export interface HostListQueryResult { resultLength: number; resultList: HostMetadata[]; - queryStrategyVersion: MetadataQueryStrategyVersions; } export interface HostQueryResult { resultLength: number; result: HostMetadata | undefined; - queryStrategyVersion: MetadataQueryStrategyVersions; -} - -export interface MetadataQueryStrategy { - index: string; - extraBodyProperties?: JsonObject; - queryResponseToHostListResult: ( - searchResponse: SearchResponse - ) => HostListQueryResult; - queryResponseToHostResult: (searchResponse: SearchResponse) => HostQueryResult; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index f4d942f733c1d..9b9f49a167397 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -199,10 +199,10 @@ export const getHostEndpoint = async ( }; const endpointData = id != null && metadataRequestContext.endpointAppContextService.getAgentService() != null - ? await getHostMetaData(metadataRequestContext, id, undefined) + ? await getHostMetaData(metadataRequestContext, id) : null; - const fleetAgentId = endpointData?.metadata.elastic.agent.id; + const fleetAgentId = endpointData?.elastic.agent.id; const [fleetAgentStatus, pendingActions] = !fleetAgentId ? [undefined, {}] : await Promise.all([ @@ -214,13 +214,13 @@ export const getHostEndpoint = async ( }), ]); - return endpointData != null && endpointData.metadata + return endpointData != null && endpointData ? { - endpointPolicy: endpointData.metadata.Endpoint.policy.applied.name, - policyStatus: endpointData.metadata.Endpoint.policy.applied.status, - sensorVersion: endpointData.metadata.agent.version, + endpointPolicy: endpointData.Endpoint.policy.applied.name, + policyStatus: endpointData.Endpoint.policy.applied.status, + sensorVersion: endpointData.agent.version, elasticAgentStatus: fleetAgentStatusToEndpointHostStatus(fleetAgentStatus!), - isolation: endpointData.metadata.Endpoint.state?.isolation ?? false, + isolation: endpointData.Endpoint.state?.isolation ?? false, pendingActions, } : null; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index 1a52bd18f80af..e1763b6ad4404 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -29,7 +29,6 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider }); loadTestFile(require.resolve('./resolver/index')); loadTestFile(require.resolve('./metadata')); - loadTestFile(require.resolve('./metadata_v1')); loadTestFile(require.resolve('./policy')); loadTestFile(require.resolve('./package')); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index b5d98c115d194..1f57cd1b6db34 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -12,7 +12,6 @@ import { deleteAllDocsFromMetadataIndex, deleteMetadataStream, } from './data_stream_helper'; -import { MetadataQueryStrategyVersions } from '../../../plugins/security_solution/common/endpoint/types'; import { HOST_METADATA_LIST_ROUTE } from '../../../plugins/security_solution/common/endpoint/constants'; /** @@ -88,7 +87,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(1); expect(body.request_page_size).to.eql(1); expect(body.request_page_index).to.eql(1); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); /* test that when paging properties produces no result, the total should reflect the actual number of metadata @@ -113,7 +111,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(0); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(30); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return 400 when pagingProperties is below boundaries.', async () => { @@ -148,7 +145,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(2); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return page based on filters and paging passed.', async () => { @@ -186,7 +182,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(2); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return page based on host.os.Ext.variant filter.', async () => { @@ -208,7 +203,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(2); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return the latest event for all the events for an endpoint', async () => { @@ -231,7 +225,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(1); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return the latest event for all the events where policy status is not success', async () => { @@ -275,7 +268,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(1); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return all hosts when filter is empty string', async () => { @@ -292,7 +284,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(numberOfHostsInFixture); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); }); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts deleted file mode 100644 index d8cf1a11fac0a..0000000000000 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts +++ /dev/null @@ -1,290 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; -import { deleteMetadataStream } from './data_stream_helper'; -import { METADATA_REQUEST_V1_ROUTE } from '../../../plugins/security_solution/server/endpoint/routes/metadata'; -import { MetadataQueryStrategyVersions } from '../../../plugins/security_solution/common/endpoint/types'; - -/** - * The number of host documents in the es archive. - */ -const numberOfHostsInFixture = 3; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - describe('test metadata api v1', () => { - describe(`POST ${METADATA_REQUEST_V1_ROUTE} when index is empty`, () => { - it('metadata api should return empty result when index is empty', async () => { - // the endpoint uses data streams and es archiver does not support deleting them at the moment so we need - // to do it manually - await deleteMetadataStream(getService); - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send() - .expect(200); - expect(body.total).to.eql(0); - expect(body.hosts.length).to.eql(0); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - }); - - describe(`POST ${METADATA_REQUEST_V1_ROUTE} when index is not empty`, () => { - before( - async () => - await esArchiver.load( - 'x-pack/test/functional/es_archives/endpoint/metadata/api_feature', - { useCreate: true } - ) - ); - // the endpoint uses data streams and es archiver does not support deleting them at the moment so we need - // to do it manually - after(async () => await deleteMetadataStream(getService)); - it('metadata api should return one entry for each host with default paging', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send() - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(numberOfHostsInFixture); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return page based on paging properties passed.', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 1, - }, - { - page_index: 1, - }, - ], - }) - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(1); - expect(body.request_page_size).to.eql(1); - expect(body.request_page_index).to.eql(1); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - /* test that when paging properties produces no result, the total should reflect the actual number of metadata - in the index. - */ - it('metadata api should return accurate total metadata if page index produces no result', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 3, - }, - ], - }) - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(0); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(30); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return 400 when pagingProperties is below boundaries.', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 0, - }, - { - page_index: 1, - }, - ], - }) - .expect(400); - expect(body.message).to.contain('Value must be equal to or greater than [1]'); - }); - - it('metadata api should return page based on filters passed.', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: 'not host.ip:10.46.229.234', - }, - }) - .expect(200); - expect(body.total).to.eql(2); - expect(body.hosts.length).to.eql(2); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return page based on filters and paging passed.', async () => { - const notIncludedIp = '10.46.229.234'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 0, - }, - ], - filters: { - kql: `not host.ip:${notIncludedIp}`, - }, - }) - .expect(200); - expect(body.total).to.eql(2); - const resultIps: string[] = [].concat( - ...body.hosts.map((hostInfo: Record) => hostInfo.metadata.host.ip) - ); - expect(resultIps).to.eql([ - '10.192.213.130', - '10.70.28.129', - '10.101.149.26', - '2606:a000:ffc0:39:11ef:37b9:3371:578c', - ]); - expect(resultIps).not.include.eql(notIncludedIp); - expect(body.hosts.length).to.eql(2); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return page based on host.os.Ext.variant filter.', async () => { - const variantValue = 'Windows Pro'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `host.os.Ext.variant:${variantValue}`, - }, - }) - .expect(200); - expect(body.total).to.eql(2); - const resultOsVariantValue: Set = new Set( - body.hosts.map((hostInfo: Record) => hostInfo.metadata.host.os.Ext.variant) - ); - expect(Array.from(resultOsVariantValue)).to.eql([variantValue]); - expect(body.hosts.length).to.eql(2); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return the latest event for all the events for an endpoint', async () => { - const targetEndpointIp = '10.46.229.234'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `host.ip:${targetEndpointIp}`, - }, - }) - .expect(200); - expect(body.total).to.eql(1); - const resultIp: string = body.hosts[0].metadata.host.ip.filter( - (ip: string) => ip === targetEndpointIp - ); - expect(resultIp).to.eql([targetEndpointIp]); - expect(body.hosts[0].metadata.event.created).to.eql(1618841405309); - expect(body.hosts.length).to.eql(1); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return the latest event for all the events where policy status is not success', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `not Endpoint.policy.applied.status:success`, - }, - }) - .expect(200); - const statuses: Set = new Set( - body.hosts.map( - (hostInfo: Record) => hostInfo.metadata.Endpoint.policy.applied.status - ) - ); - expect(statuses.size).to.eql(1); - expect(Array.from(statuses)).to.eql(['failure']); - }); - - it('metadata api should return the endpoint based on the elastic agent id, and status should be unhealthy', async () => { - const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf'; - const targetElasticAgentId = '023fa40c-411d-4188-a941-4147bfadd095'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `elastic.agent.id:${targetElasticAgentId}`, - }, - }) - .expect(200); - expect(body.total).to.eql(1); - const resultHostId: string = body.hosts[0].metadata.host.id; - const resultElasticAgentId: string = body.hosts[0].metadata.elastic.agent.id; - expect(resultHostId).to.eql(targetEndpointId); - expect(resultElasticAgentId).to.eql(targetElasticAgentId); - expect(body.hosts[0].metadata.event.created).to.eql(1618841405309); - expect(body.hosts[0].host_status).to.eql('unhealthy'); - expect(body.hosts.length).to.eql(1); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return all hosts when filter is empty string', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: '', - }, - }) - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(numberOfHostsInFixture); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - }); - }); -} From b2b57a240423e9503d1307075d34c73eb8b2a3c9 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Mon, 12 Jul 2021 00:02:20 -0700 Subject: [PATCH 13/31] [dev-docs] Add debugging tutorial (#104468) Signed-off-by: Tyler Smalley Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/tutorials/debugging.mdx | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 dev_docs/tutorials/debugging.mdx diff --git a/dev_docs/tutorials/debugging.mdx b/dev_docs/tutorials/debugging.mdx new file mode 100644 index 0000000000000..c0efd249be066 --- /dev/null +++ b/dev_docs/tutorials/debugging.mdx @@ -0,0 +1,61 @@ +--- +id: kibDevTutorialDebugging +slug: /kibana-dev-docs/tutorial/debugging +title: Debugging in development +summary: Learn how to debug Kibana while running from source +date: 2021-04-26 +tags: ['kibana', 'onboarding', 'dev', 'tutorials', 'debugging'] +--- + +There are multiple ways to go about debugging Kibana when running from source. + +## Debugging using Chrome DevTools + +You will need to run Node using `--inspect` or `--inspect-brk` in order to enable the inspector. Additional information can be found in the [Node.js docs](https://nodejs.org/en/docs/guides/debugging-getting-started/). + +Once Node is running with the inspector enabled, you can open `chrome://inspect` in your Chrome browser. You should see a remote target for the inspector running. Click "inspect". You can now begin using the debugger. + +Next we will go over how to exactly enable the inspector for different aspects of the codebase. + +### Jest Unit Tests + +You will need to run Jest directly from the Node script: + +`node --inspect-brk scripts/jest [TestPathPattern]` + +### Functional Test Runner + +`node --inspect-brk scripts/functional_test_runner` + +### Development Server + +`node --inspect-brk scripts/kibana` + +## Debugging using logging + +When running Kibana, it's sometimes helpful to enable verbose logging. + +`yarn start --verbose` + +Using verbose logging usually results in much more information than you're interested in. The [logging documentation](https://www.elastic.co/guide/en/kibana/current/logging-settings.html) covers ways to change the log level of certain types. + +In the following example of a configuration stored in `config/kibana.dev.yml` we are logging all Elasticsearch queries and any logs created by the Management plugin. + +``` +logging: + appenders: + console: + type: console + layout: + type: pattern + highlight: true + root: + appenders: [default, console] + level: info + + loggers: + - name: plugins.management + level: debug + - name: elasticsearch.query + level: debug +``` \ No newline at end of file From 5256b6d23ef4d05ff3dce1d7d91711c1748c8d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 12 Jul 2021 11:46:38 +0200 Subject: [PATCH 14/31] Accomodate height of the (#104882) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../sections/agent_policy/edit_package_policy_page/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index b07d76dc6bd8e..ee529b6865e56 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -430,7 +430,9 @@ export const EditPackagePolicyForm = memo<{ /> )} {configurePackage} - + {/* Extra space to accomodate the EuiBottomBar height */} + + From b8dfcafe38e636ec88b2ca03253cf15cec844d27 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Mon, 12 Jul 2021 11:31:54 +0100 Subject: [PATCH 15/31] [ML] Fixes unnecessary too many buckets warning on anomaly chart embeddable (#105043) * [ML] Fixes unnecessary too many buckets warning on anomaly chart embeddable * [ML] Update jest tests for number of axis ticks. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../explorer_chart_distribution.js | 24 ++++++++++--------- .../explorer_chart_distribution.test.js | 2 +- .../explorer_chart_single_metric.js | 24 ++++++++++--------- .../explorer_chart_single_metric.test.js | 2 +- .../anomaly_explorer_charts_service.ts | 11 +++++++-- 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js index 7efd36bbe57c6..27a934fa841fe 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js @@ -270,12 +270,6 @@ export class ExplorerChartDistribution extends React.Component { const tickValuesStart = Math.max(config.selectedEarliest, config.plotEarliest); // +1 ms to account for the ms that was subtracted for query aggregations. const interval = config.selectedLatest - config.selectedEarliest + 1; - const tickValues = getTickValues( - tickValuesStart, - interval, - config.plotEarliest, - config.plotLatest - ); const xAxis = d3.svg .axis() @@ -286,10 +280,18 @@ export class ExplorerChartDistribution extends React.Component { .tickPadding(10) .tickFormat((d) => moment(d).format(xAxisTickFormat)); - // With tooManyBuckets the chart would end up with no x-axis labels - // because the ticks are based on the span of the emphasis section, - // and the highlighted area spans the whole chart. - if (tooManyBuckets === false) { + // With tooManyBuckets, or when the chart is used as an embeddable, + // the chart would end up with no x-axis labels because the ticks are based on the span of the + // emphasis section, and the selected area spans the whole chart. + const useAutoTicks = + tooManyBuckets === true || interval >= config.plotLatest - config.plotEarliest; + if (useAutoTicks === false) { + const tickValues = getTickValues( + tickValuesStart, + interval, + config.plotEarliest, + config.plotLatest + ); xAxis.tickValues(tickValues); } else { xAxis.ticks(numTicksForDateFormat(vizWidth, xAxisTickFormat)); @@ -327,7 +329,7 @@ export class ExplorerChartDistribution extends React.Component { }); } - if (tooManyBuckets === false) { + if (useAutoTicks === false) { removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth); } } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js index 11a15b192fc52..8d2f66a870c75 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js @@ -139,7 +139,7 @@ describe('ExplorerChart', () => { expect(+selectedInterval.getAttribute('height')).toBe(166); const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick'); - expect([...xAxisTicks]).toHaveLength(0); + expect([...xAxisTicks]).toHaveLength(1); const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(5); const emphasizedAxisLabel = wrapper diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index dd07a7d6cbdee..19390017244a8 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -196,12 +196,6 @@ export class ExplorerChartSingleMetric extends React.Component { const tickValuesStart = Math.max(config.selectedEarliest, config.plotEarliest); // +1 ms to account for the ms that was subtracted for query aggregations. const interval = config.selectedLatest - config.selectedEarliest + 1; - const tickValues = getTickValues( - tickValuesStart, - interval, - config.plotEarliest, - config.plotLatest - ); const xAxis = d3.svg .axis() @@ -212,10 +206,18 @@ export class ExplorerChartSingleMetric extends React.Component { .tickPadding(10) .tickFormat((d) => moment(d).format(xAxisTickFormat)); - // With tooManyBuckets the chart would end up with no x-axis labels - // because the ticks are based on the span of the emphasis section, - // and the highlighted area spans the whole chart. - if (tooManyBuckets === false) { + // With tooManyBuckets, or when the chart is used as an embeddable, + // the chart would end up with no x-axis labels because the ticks are based on the span of the + // emphasis section, and the selected area spans the whole chart. + const useAutoTicks = + tooManyBuckets === true || interval >= config.plotLatest - config.plotEarliest; + if (useAutoTicks === false) { + const tickValues = getTickValues( + tickValuesStart, + interval, + config.plotEarliest, + config.plotLatest + ); xAxis.tickValues(tickValues); } else { xAxis.ticks(numTicksForDateFormat(vizWidth, xAxisTickFormat)); @@ -243,7 +245,7 @@ export class ExplorerChartSingleMetric extends React.Component { axes.append('g').attr('class', 'y axis').call(yAxis); - if (tooManyBuckets === false) { + if (useAutoTicks === false) { removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth); } } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js index 981f7515d3d70..00172965bc216 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -144,7 +144,7 @@ describe('ExplorerChart', () => { expect(+selectedInterval.getAttribute('height')).toBe(166); const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick'); - expect([...xAxisTicks]).toHaveLength(0); + expect([...xAxisTicks]).toHaveLength(1); const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(10); diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts index afad043fcc4d1..97ddefac860f2 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts @@ -225,13 +225,20 @@ export class AnomalyExplorerChartsService { chartRange.min = chartRange.min + maxBucketSpanMs; } + // When used as an embeddable, selectedEarliestMs is the start date on the time picker, + // which may be earlier than the time of the first point plotted in the chart (as we plot + // the first full bucket with a start date no earlier than the start). + const selectedEarliestBucketCeil = boundsMin + ? Math.ceil(Math.max(selectedEarliestMs, boundsMin) / maxBucketSpanMs) * maxBucketSpanMs + : Math.ceil(selectedEarliestMs / maxBucketSpanMs) * maxBucketSpanMs; + const selectedLatestBucketStart = boundsMax ? Math.floor(Math.min(selectedLatestMs, boundsMax) / maxBucketSpanMs) * maxBucketSpanMs : Math.floor(selectedLatestMs / maxBucketSpanMs) * maxBucketSpanMs; if ( - (chartRange.min > selectedEarliestMs || chartRange.max < selectedLatestBucketStart) && - chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestMs + (chartRange.min > selectedEarliestBucketCeil || chartRange.max < selectedLatestBucketStart) && + chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestBucketCeil ) { tooManyBuckets = true; } From 216bb5e1b8c8406cd23503619972e5a3ea543790 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Mon, 12 Jul 2021 13:23:08 +0200 Subject: [PATCH 16/31] [load] run puppeteer script before gatling scenarios (#104836) * [load] puppeteer script before load testing * install dependencies after metricbeat configuration Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/scripts/jenkins_build_load_testing.sh | 3 +++ x-pack/test/load/runner.ts | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh index d7c7bda83c9ef..667540515fc83 100755 --- a/test/scripts/jenkins_build_load_testing.sh +++ b/test/scripts/jenkins_build_load_testing.sh @@ -53,6 +53,9 @@ echo "cloud.auth: ${USER_FROM_VAULT}:${PASS_FROM_VAULT}" >> cfg/metricbeat/metri cp cfg/metricbeat/metricbeat.yml $KIBANA_DIR/metricbeat-install/metricbeat.yml # Disable system monitoring: enabled for now to have more data #mv $KIBANA_DIR/metricbeat-install/modules.d/system.yml $KIBANA_DIR/metricbeat-install/modules.d/system.yml.disabled +echo " -> Building puppeteer project" +cd puppeteer +yarn install && yarn build popd # doesn't persist, also set in kibanaPipeline.groovy diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts index 2d379391b2089..0bea5992f5539 100644 --- a/x-pack/test/load/runner.ts +++ b/x-pack/test/load/runner.ts @@ -18,6 +18,7 @@ const simulationPackage = 'org.kibanaLoadTest.simulation'; const simulationFIleExtension = '.scala'; const gatlingProjectRootPath: string = process.env.GATLING_PROJECT_PATH || resolve(REPO_ROOT, '../kibana-load-testing'); +const puppeteerProjectRootPath: string = resolve(gatlingProjectRootPath, 'puppeteer'); const simulationEntry: string = process.env.GATLING_SIMULATIONS || 'branch.DemoJourney'; if (!Fs.existsSync(gatlingProjectRootPath)) { @@ -52,6 +53,15 @@ export async function GatlingTestRunner({ getService }: FtrProviderContext) { const log = getService('log'); await withProcRunner(log, async (procs) => { + await procs.run('node build/index.js', { + cmd: 'node', + args: ['build/index.js'], + cwd: puppeteerProjectRootPath, + env: { + ...process.env, + }, + wait: true, + }); for (let i = 0; i < simulationClasses.length; i++) { await procs.run('gatling: test', { cmd: 'mvn', From 832d349930be1f8d4a7a47733f413b0ab75303e8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 12 Jul 2021 14:13:42 +0200 Subject: [PATCH 17/31] [KibanaLegacy] Remove unused stuff and make things async if it is easy (#104638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove unused stuff and make things async if it is easy * fix problems * load bootstrap in monitoring * load angular bootstrap for saved searches and in unit tests * fix vis_type_table tests * Update x-pack/plugins/monitoring/public/plugin.ts Co-authored-by: Ester Martí Vilaseca * Update x-pack/plugins/monitoring/public/plugin.ts Co-authored-by: Ester Martí Vilaseca Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli Co-authored-by: Ester Martí Vilaseca --- .../doc_table/components/row_headers.test.js | 4 + .../angular/doc_table/doc_table.test.js | 4 + .../application/angular/get_inner_angular.ts | 4 +- .../application/angular/helpers/index.ts | 1 + .../angular/helpers}/promises.d.ts | 0 .../application/angular/helpers}/promises.js | 0 src/plugins/discover/public/plugin.tsx | 2 + .../public/angular/angular_config.tsx | 7 +- .../kibana_legacy/public/angular/index.ts | 2 - src/plugins/kibana_legacy/public/index.ts | 1 - src/plugins/kibana_legacy/public/mocks.ts | 1 + .../kibana_legacy/public/notify/index.ts | 1 - .../notify/toasts/TOAST_NOTIFICATIONS.md | 100 ------------------ .../public/notify/toasts/index.ts | 9 -- .../notify/toasts/toast_notifications.test.ts | 76 ------------- .../notify/toasts/toast_notifications.ts | 37 ------- src/plugins/kibana_legacy/public/plugin.ts | 8 ++ .../kibana_legacy/public/utils/index.ts | 1 - .../kibana_legacy/public/utils/system_api.ts | 40 ------- src/plugins/timelion/public/plugin.ts | 5 +- .../public/legacy/agg_table/agg_table.test.js | 6 +- .../legacy/agg_table/agg_table_group.test.js | 6 +- .../public/legacy/get_inner_angular.ts | 3 - .../paginated_table/paginated_table.test.ts | 5 + .../legacy/table_vis_controller.test.ts | 4 + .../public/legacy/vis_controller.ts | 1 + x-pack/plugins/graph/public/plugin.ts | 7 +- x-pack/plugins/monitoring/public/plugin.ts | 5 +- 28 files changed, 54 insertions(+), 286 deletions(-) rename src/plugins/{kibana_legacy/public/angular => discover/public/application/angular/helpers}/promises.d.ts (100%) rename src/plugins/{kibana_legacy/public/angular => discover/public/application/angular/helpers}/promises.js (100%) delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/index.ts delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts delete mode 100644 src/plugins/kibana_legacy/public/utils/system_api.ts diff --git a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js b/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js index a087ac8697183..1a3b34c45d05e 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js +++ b/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js @@ -19,6 +19,7 @@ import { setScopedHistory, setServices, setDocViewsRegistry } from '../../../../ import { coreMock } from '../../../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../../../data/public/mocks'; import { navigationPluginMock } from '../../../../../../navigation/public/mocks'; +import { initAngularBootstrap } from '../../../../../../kibana_legacy/public/angular_bootstrap'; import { getInnerAngularModule } from '../../get_inner_angular'; import { createBrowserHistory } from 'history'; @@ -41,6 +42,9 @@ describe('Doc Table', () => { // Stub out a minimal mapping of 4 fields let mapping; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeAll(() => setScopedHistory(createBrowserHistory())); beforeEach(() => { angular.element.prototype.slice = jest.fn(function (index) { diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js b/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js index 1db35ddf18089..097f32965b141 100644 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js +++ b/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js @@ -17,6 +17,7 @@ import hits from '../../../__fixtures__/real_hits'; import { coreMock } from '../../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../../data/public/mocks'; import { navigationPluginMock } from '../../../../../navigation/public/mocks'; +import { initAngularBootstrap } from '../../../../../kibana_legacy/public/angular_bootstrap'; import { setScopedHistory, setServices } from '../../../kibana_services'; import { getInnerAngularModule } from '../get_inner_angular'; @@ -54,6 +55,9 @@ describe('docTable', () => { const core = coreMock.createStart(); let $elem; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeAll(() => setScopedHistory(createBrowserHistory())); beforeEach(() => { angular.element.prototype.slice = jest.fn(() => { diff --git a/src/plugins/discover/public/application/angular/get_inner_angular.ts b/src/plugins/discover/public/application/angular/get_inner_angular.ts index 26d64d5adc8a3..992d82795302b 100644 --- a/src/plugins/discover/public/application/angular/get_inner_angular.ts +++ b/src/plugins/discover/public/application/angular/get_inner_angular.ts @@ -33,13 +33,12 @@ import { createDocViewerDirective } from './doc_viewer'; import { createDiscoverGridDirective } from './create_discover_grid_directive'; import { createRenderCompleteDirective } from './directives/render_complete'; import { - initAngularBootstrap, configureAppAngularModule, PrivateProvider, - PromiseServiceCreator, registerListenEventListener, watchMultiDecorator, } from '../../../../kibana_legacy/public'; +import { PromiseServiceCreator } from './helpers'; import { DiscoverStartPlugins } from '../../plugin'; import { getScopedHistory } from '../../kibana_services'; import { createDiscoverDirective } from './create_discover_directive'; @@ -54,7 +53,6 @@ export function getInnerAngularModule( deps: DiscoverStartPlugins, context: PluginInitializerContext ) { - initAngularBootstrap(); const module = initializeInnerAngularModule(name, core, deps.navigation, deps.data); configureAppAngularModule(module, { core, env: context.env }, true, getScopedHistory); return module; diff --git a/src/plugins/discover/public/application/angular/helpers/index.ts b/src/plugins/discover/public/application/angular/helpers/index.ts index 3d2c0b1c63b33..6a7f75b7e81a2 100644 --- a/src/plugins/discover/public/application/angular/helpers/index.ts +++ b/src/plugins/discover/public/application/angular/helpers/index.ts @@ -8,3 +8,4 @@ export { formatRow, formatTopLevelObject } from './row_formatter'; export { handleSourceColumnState } from './state_helpers'; +export { PromiseServiceCreator } from './promises'; diff --git a/src/plugins/kibana_legacy/public/angular/promises.d.ts b/src/plugins/discover/public/application/angular/helpers/promises.d.ts similarity index 100% rename from src/plugins/kibana_legacy/public/angular/promises.d.ts rename to src/plugins/discover/public/application/angular/helpers/promises.d.ts diff --git a/src/plugins/kibana_legacy/public/angular/promises.js b/src/plugins/discover/public/application/angular/helpers/promises.js similarity index 100% rename from src/plugins/kibana_legacy/public/angular/promises.js rename to src/plugins/discover/public/application/angular/helpers/promises.js diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 3e31fe1d46d45..1e8a5cdac95ef 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -403,6 +403,7 @@ export class DiscoverPlugin } // this is used by application mount and tests const { getInnerAngularModule } = await import('./application/angular/get_inner_angular'); + await plugins.kibanaLegacy.loadAngularBootstrap(); const module = getInnerAngularModule( innerAngularName, core, @@ -473,6 +474,7 @@ export class DiscoverPlugin throw Error('Discover plugin getEmbeddableInjector: initializeServices is undefined'); } const { core, plugins } = await this.initializeServices(); + await getServices().kibanaLegacy.loadAngularBootstrap(); getServices().kibanaLegacy.loadFontAwesome(); const { getInnerAngularModuleEmbeddable } = await import( './application/angular/get_inner_angular' diff --git a/src/plugins/kibana_legacy/public/angular/angular_config.tsx b/src/plugins/kibana_legacy/public/angular/angular_config.tsx index daecfbc57ea99..48ee6d2db269e 100644 --- a/src/plugins/kibana_legacy/public/angular/angular_config.tsx +++ b/src/plugins/kibana_legacy/public/angular/angular_config.tsx @@ -13,6 +13,7 @@ import { ILocationProvider, IModule, IRootScopeService, + IRequestConfig, } from 'angular'; import $ from 'jquery'; import { set } from '@elastic/safer-lodash-set'; @@ -22,7 +23,6 @@ import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'kibana/public'; import { History } from 'history'; import { CoreStart } from 'kibana/public'; -import { isSystemApiRequest } from '../utils'; import { formatAngularHttpError, isAngularHttpError } from '../notify/lib'; export interface RouteConfiguration { @@ -38,6 +38,11 @@ export interface RouteConfiguration { requireUICapability?: string; } +function isSystemApiRequest(request: IRequestConfig) { + const { headers } = request; + return headers && !!headers['kbn-system-request']; +} + /** * Detects whether a given angular route is a dummy route that doesn't * require any action. There are two ways this can happen: diff --git a/src/plugins/kibana_legacy/public/angular/index.ts b/src/plugins/kibana_legacy/public/angular/index.ts index d9d8c0c19eb7b..369495698591d 100644 --- a/src/plugins/kibana_legacy/public/angular/index.ts +++ b/src/plugins/kibana_legacy/public/angular/index.ts @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -// @ts-ignore -export { PromiseServiceCreator } from './promises'; // @ts-ignore export { watchMultiDecorator } from './watch_multi'; export * from './angular_config'; diff --git a/src/plugins/kibana_legacy/public/index.ts b/src/plugins/kibana_legacy/public/index.ts index 03adb768cde20..ea5172f78a68f 100644 --- a/src/plugins/kibana_legacy/public/index.ts +++ b/src/plugins/kibana_legacy/public/index.ts @@ -14,7 +14,6 @@ export const plugin = (initializerContext: PluginInitializerContext) => export * from './plugin'; -export { initAngularBootstrap } from './angular_bootstrap'; export { PaginateDirectiveProvider, PaginateControlsDirectiveProvider } from './paginate/paginate'; export * from './angular'; export * from './notify'; diff --git a/src/plugins/kibana_legacy/public/mocks.ts b/src/plugins/kibana_legacy/public/mocks.ts index 40834635cc570..6116c0682cb3b 100644 --- a/src/plugins/kibana_legacy/public/mocks.ts +++ b/src/plugins/kibana_legacy/public/mocks.ts @@ -22,6 +22,7 @@ const createStartContract = (): Start => ({ getHideWriteControls: jest.fn(), }, loadFontAwesome: jest.fn(), + loadAngularBootstrap: jest.fn(), }); export const kibanaLegacyPluginMock = { diff --git a/src/plugins/kibana_legacy/public/notify/index.ts b/src/plugins/kibana_legacy/public/notify/index.ts index a243059cb1918..d4dcaa77cc47a 100644 --- a/src/plugins/kibana_legacy/public/notify/index.ts +++ b/src/plugins/kibana_legacy/public/notify/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export * from './toasts'; export * from './lib'; diff --git a/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md b/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md deleted file mode 100644 index de6a51f3927d1..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md +++ /dev/null @@ -1,100 +0,0 @@ -# Toast notifications - -Use this service to surface toasts in the bottom-right corner of the screen. After a brief delay, they'll disappear. They're useful for notifying the user of state changes. See [the EUI docs](https://elastic.github.io/eui/) for more information on toasts and their role within the UI. - -## Importing the module - -```js -import { toastNotifications } from 'ui/notify'; -``` - -## Interface - -### Adding toasts - -For convenience, there are several methods which predefine the appearance of different types of toasts. Use these methods so that the same types of toasts look similar to the user. - -#### Default - -Neutral toast. Tell the user a change in state has occurred, which is not necessarily good or bad. - -```js -toastNotifications.add('Copied to clipboard'); -``` - -#### Success - -Let the user know that an action was successful, such as saving or deleting an object. - -```js -toastNotifications.addSuccess('Your document was saved'); -``` - -#### Warning - -If something OK or good happened, but perhaps wasn't perfect, show a warning toast. - -```js -toastNotifications.addWarning('Your document was saved, but not its edit history'); -``` - -#### Danger - -When the user initiated an action but the action failed, show them a danger toast. - -```js -toastNotifications.addDanger('An error caused your document to be lost'); -``` - -### Removing a toast - -Toasts will automatically be dismissed after a brief delay, but if for some reason you want to dismiss a toast, you can use the returned toast from one of the `add` methods and then pass it to `remove`. - -```js -const toast = toastNotifications.add('Your document was saved'); -toastNotifications.remove(toast); -``` - -### Configuration options - -If you want to configure the toast further you can provide an object instead of a string. The properties of this object correspond to the `propTypes` accepted by the `EuiToast` component. Refer to [the EUI docs](https://elastic.github.io/eui/) for info on these `propTypes`. - -```js -toastNotifications.add({ - title: 'Your document was saved', - text: 'Only you have access to this document', - color: 'success', - iconType: 'check', - 'data-test-subj': 'saveDocumentSuccess', -}); -``` - -Because the underlying components are React, you can use JSX to pass in React elements to the `text` prop. This gives you total flexibility over the content displayed within the toast. - -```js -toastNotifications.add({ - title: 'Your document was saved', - text: ( -
-

- Only you have access to this document. Edit permissions. -

- - -
- ), -}); -``` - -## Use in functional tests - -Functional tests are commonly used to verify that a user action yielded a successful outcome. If you surface a toast to notify the user of this successful outcome, you can place a `data-test-subj` attribute on the toast and use it to check if the toast exists inside of your functional test. This acts as a proxy for verifying the successful outcome. - -```js -toastNotifications.addSuccess({ - title: 'Your document was saved', - 'data-test-subj': 'saveDocumentSuccess', -}); -``` diff --git a/src/plugins/kibana_legacy/public/notify/toasts/index.ts b/src/plugins/kibana_legacy/public/notify/toasts/index.ts deleted file mode 100644 index cdd7df04548fb..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/index.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { ToastNotifications } from './toast_notifications'; diff --git a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts b/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts deleted file mode 100644 index c2c5d9a4fc014..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts +++ /dev/null @@ -1,76 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { notificationServiceMock } from '../../../../../core/public/mocks'; -import { ToastNotifications } from './toast_notifications'; -import { Toast } from 'kibana/public'; -import { BehaviorSubject } from 'rxjs'; - -describe('ToastNotifications', () => { - describe('interface', () => { - function setup() { - const toastsMock = notificationServiceMock.createStartContract().toasts; - return { toastNotifications: new ToastNotifications(toastsMock), toastsMock }; - } - - describe('add method', () => { - test('adds a toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.add({}); - expect(toastsMock.add).toHaveBeenCalled(); - }); - }); - - describe('remove method', () => { - test('removes a toast', () => { - const { toastNotifications, toastsMock } = setup(); - const fakeToast = {} as Toast; - toastNotifications.remove(fakeToast); - expect(toastsMock.remove).toHaveBeenCalledWith(fakeToast); - }); - }); - - describe('onChange method', () => { - test('callback is called when observable changes', () => { - const toastsMock = notificationServiceMock.createStartContract().toasts; - const toasts$ = new BehaviorSubject([]); - toastsMock.get$.mockReturnValue(toasts$); - const toastNotifications = new ToastNotifications(toastsMock); - const onChangeSpy = jest.fn(); - toastNotifications.onChange(onChangeSpy); - toasts$.next([{ id: 'toast1' }]); - toasts$.next([]); - expect(onChangeSpy).toHaveBeenCalledTimes(2); - }); - }); - - describe('addSuccess method', () => { - test('adds a success toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.addSuccess({}); - expect(toastsMock.addSuccess).toHaveBeenCalled(); - }); - }); - - describe('addWarning method', () => { - test('adds a warning toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.addWarning({}); - expect(toastsMock.addWarning).toHaveBeenCalled(); - }); - }); - - describe('addDanger method', () => { - test('adds a danger toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.addWarning({}); - expect(toastsMock.addWarning).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts b/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts deleted file mode 100644 index e7ccbbca07b73..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts +++ /dev/null @@ -1,37 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { NotificationsSetup, Toast, ToastInput, ErrorToastOptions } from 'kibana/public'; - -export class ToastNotifications { - public list: Toast[] = []; - - private onChangeCallback?: () => void; - - constructor(private readonly toasts: NotificationsSetup['toasts']) { - toasts.get$().subscribe((list) => { - this.list = list; - - if (this.onChangeCallback) { - this.onChangeCallback(); - } - }); - } - - public onChange = (callback: () => void) => { - this.onChangeCallback = callback; - }; - - public add = (toastOrTitle: ToastInput) => this.toasts.add(toastOrTitle); - public remove = (toast: Toast) => this.toasts.remove(toast); - public addSuccess = (toastOrTitle: ToastInput) => this.toasts.addSuccess(toastOrTitle); - public addWarning = (toastOrTitle: ToastInput) => this.toasts.addWarning(toastOrTitle); - public addDanger = (toastOrTitle: ToastInput) => this.toasts.addDanger(toastOrTitle); - public addError = (error: Error, options: ErrorToastOptions) => - this.toasts.addError(error, options); -} diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts index 337fdb80da7e4..f60130d367b58 100644 --- a/src/plugins/kibana_legacy/public/plugin.ts +++ b/src/plugins/kibana_legacy/public/plugin.ts @@ -33,6 +33,14 @@ export class KibanaLegacyPlugin { loadFontAwesome: async () => { await import('./font_awesome'); }, + /** + * Loads angular bootstrap modules. Should be removed once the last consumer has migrated to EUI + * @deprecated + */ + loadAngularBootstrap: async () => { + const { initAngularBootstrap } = await import('./angular_bootstrap'); + initAngularBootstrap(); + }, /** * @deprecated * Just exported for wiring up with dashboard mode, should not be used. diff --git a/src/plugins/kibana_legacy/public/utils/index.ts b/src/plugins/kibana_legacy/public/utils/index.ts index db3c0af6c8cb9..94233558b4627 100644 --- a/src/plugins/kibana_legacy/public/utils/index.ts +++ b/src/plugins/kibana_legacy/public/utils/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export * from './system_api'; // @ts-ignore export { KbnAccessibleClickProvider } from './kbn_accessible_click'; // @ts-ignore diff --git a/src/plugins/kibana_legacy/public/utils/system_api.ts b/src/plugins/kibana_legacy/public/utils/system_api.ts deleted file mode 100644 index d0fe221935ba5..0000000000000 --- a/src/plugins/kibana_legacy/public/utils/system_api.ts +++ /dev/null @@ -1,40 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { IRequestConfig } from 'angular'; - -const SYSTEM_REQUEST_HEADER_NAME = 'kbn-system-request'; -const LEGACY_SYSTEM_API_HEADER_NAME = 'kbn-system-api'; - -/** - * Adds a custom header designating request as system API - * @param originalHeaders Object representing set of headers - * @return Object representing set of headers, with system API header added in - */ -export function addSystemApiHeader(originalHeaders: Record) { - const systemApiHeaders = { - [SYSTEM_REQUEST_HEADER_NAME]: true, - }; - return { - ...originalHeaders, - ...systemApiHeaders, - }; -} - -/** - * Returns true if request is a system API request; false otherwise - * - * @param request Object Request object created by $http service - * @return true if request is a system API request; false otherwise - */ -export function isSystemApiRequest(request: IRequestConfig) { - const { headers } = request; - return ( - headers && (!!headers[SYSTEM_REQUEST_HEADER_NAME] || !!headers[LEGACY_SYSTEM_API_HEADER_NAME]) - ); -} diff --git a/src/plugins/timelion/public/plugin.ts b/src/plugins/timelion/public/plugin.ts index 6f8dbfdcc6704..63ea9a38e2795 100644 --- a/src/plugins/timelion/public/plugin.ts +++ b/src/plugins/timelion/public/plugin.ts @@ -19,7 +19,7 @@ import { AppNavLinkStatus, } from '../../../core/public'; import { Panel } from './panels/panel'; -import { initAngularBootstrap } from '../../kibana_legacy/public'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DataPublicPluginStart, esFilters, DataPublicPluginSetup } from '../../data/public'; import { NavigationPublicPluginStart } from '../../navigation/public'; @@ -41,6 +41,7 @@ export interface TimelionPluginStartDependencies { visualizations: VisualizationsStart; visTypeTimelion: VisTypeTimelionPluginStart; savedObjects: SavedObjectsStart; + kibanaLegacy: KibanaLegacyStart; } /** @internal */ @@ -91,7 +92,6 @@ export class TimelionPlugin stopUrlTracker(); }; - initAngularBootstrap(); core.application.register({ id: 'timelion', title: 'Timelion', @@ -103,6 +103,7 @@ export class TimelionPlugin visTypeTimelion.isUiEnabled === false ? AppNavLinkStatus.hidden : AppNavLinkStatus.default, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); + await pluginsStart.kibanaLegacy.loadAngularBootstrap(); this.currentHistory = params.history; appMounted(); diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js index 65e26ddf6e03f..cbc3db6585a7d 100644 --- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js +++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js @@ -15,7 +15,7 @@ import { round } from 'lodash'; import { getFieldFormatsRegistry } from '../../../../data/public/test_utils'; import { coreMock } from '../../../../../core/public/mocks'; -import { initAngularBootstrap } from '../../../../kibana_legacy/public'; +import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap'; import { setUiSettings } from '../../../../data/public/services'; import { UI_SETTINGS } from '../../../../data/public/'; import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../../share/public'; @@ -60,10 +60,12 @@ describe('Table Vis - AggTable Directive', function () { initTableVisLegacyModule(tableVisModule); }; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeEach(() => { setUiSettings(core.uiSettings); setFormatService(getFieldFormatsRegistry(core)); - initAngularBootstrap(); initLocalAngular(); angular.mock.module('kibana/table_vis'); angular.mock.inject(($injector, config) => { diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js index 1c6630e30e5f7..ba04b2f449f6d 100644 --- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js +++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js @@ -13,11 +13,11 @@ import expect from '@kbn/expect'; import { getFieldFormatsRegistry } from '../../../../data/public/test_utils'; import { coreMock } from '../../../../../core/public/mocks'; -import { initAngularBootstrap } from '../../../../kibana_legacy/public'; import { setUiSettings } from '../../../../data/public/services'; import { setFormatService } from '../../services'; import { getInnerAngular } from '../get_inner_angular'; import { initTableVisLegacyModule } from '../table_vis_legacy_module'; +import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap'; import { tabifiedData } from './tabified_data'; const uiSettings = new Map(); @@ -40,10 +40,12 @@ describe('Table Vis - AggTableGroup Directive', function () { initTableVisLegacyModule(tableVisModule); }; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeEach(() => { setUiSettings(core.uiSettings); setFormatService(getFieldFormatsRegistry(core)); - initAngularBootstrap(); initLocalAngular(); angular.mock.module('kibana/table_vis'); angular.mock.inject(($injector) => { diff --git a/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts b/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts index 09fde318ee4df..412dd904a5e87 100644 --- a/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts +++ b/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts @@ -16,7 +16,6 @@ import 'angular-recursion'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { CoreStart, IUiSettingsClient, PluginInitializerContext } from 'kibana/public'; import { - initAngularBootstrap, PaginateDirectiveProvider, PaginateControlsDirectiveProvider, PrivateProvider, @@ -24,8 +23,6 @@ import { KbnAccessibleClickProvider, } from '../../../kibana_legacy/public'; -initAngularBootstrap(); - const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; export function getAngularModule(name: string, core: CoreStart, context: PluginInitializerContext) { diff --git a/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts b/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts index 77148803e7978..3feff52f86792 100644 --- a/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts +++ b/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts @@ -12,6 +12,7 @@ import $ from 'jquery'; import 'angular-sanitize'; import 'angular-mocks'; +import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap'; import { getAngularModule } from '../get_inner_angular'; import { initTableVisLegacyModule } from '../table_vis_legacy_module'; import { coreMock } from '../../../../../core/public/mocks'; @@ -56,6 +57,10 @@ describe('Table Vis - Paginated table', () => { const defaultPerPage = 10; let paginatedTable: any; + beforeAll(async () => { + await initAngularBootstrap(); + }); + const initLocalAngular = () => { const tableVisModule = getAngularModule( 'kibana/table_vis', diff --git a/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts b/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts index 36a9cc9cce77f..f4a742ea16cb4 100644 --- a/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts +++ b/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts @@ -13,6 +13,7 @@ import $ from 'jquery'; import { getAngularModule } from './get_inner_angular'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; +import { initAngularBootstrap } from '../../../kibana_legacy/public/angular_bootstrap'; import { tableVisLegacyTypeDefinition } from './table_vis_legacy_type'; import { Vis } from '../../../visualizations/public'; import { stubFields } from '../../../data/public/stubs'; @@ -76,6 +77,9 @@ describe('Table Vis - Controller', () => { initTableVisLegacyModule(tableVisModule); }; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeEach(initLocalAngular); beforeEach(angular.mock.module('kibana/table_vis')); diff --git a/src/plugins/vis_type_table/public/legacy/vis_controller.ts b/src/plugins/vis_type_table/public/legacy/vis_controller.ts index ee446c58c0013..ec198aa96f1f9 100644 --- a/src/plugins/vis_type_table/public/legacy/vis_controller.ts +++ b/src/plugins/vis_type_table/public/legacy/vis_controller.ts @@ -56,6 +56,7 @@ export function getTableVisualizationControllerClass( async initLocalAngular() { if (!this.tableVisModule) { const [coreStart, { kibanaLegacy }] = await core.getStartServices(); + await kibanaLegacy.loadAngularBootstrap(); this.tableVisModule = getAngularModule(innerAngularName, coreStart, context); initTableVisLegacyModule(this.tableVisModule); kibanaLegacy.loadFontAwesome(); diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 4525b42b3feb4..ec19e639b91c9 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -19,10 +19,7 @@ import { } from '../../../../src/core/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; -import { - initAngularBootstrap, - KibanaLegacyStart, -} from '../../../../src/plugins/kibana_legacy/public'; +import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; @@ -77,7 +74,6 @@ export class GraphPlugin const config = this.initializerContext.config.get(); - initAngularBootstrap(); core.application.register({ id: 'graph', title: 'Graph', @@ -88,6 +84,7 @@ export class GraphPlugin updater$: this.appUpdater$, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); + await pluginsStart.kibanaLegacy.loadAngularBootstrap(); coreStart.chrome.docTitle.change( i18n.translate('xpack.graph.pageTitle', { defaultMessage: 'Graph' }) ); diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index a5b7d4906b586..9f84165a27ba9 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -93,7 +93,10 @@ export class MonitoringPlugin category: DEFAULT_APP_CATEGORIES.management, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); - const { AngularApp } = await import('./angular'); + const [, { AngularApp }] = await Promise.all([ + pluginsStart.kibanaLegacy.loadAngularBootstrap(), + import('./angular'), + ]); const deps: MonitoringStartPluginDependencies = { navigation: pluginsStart.navigation, kibanaLegacy: pluginsStart.kibanaLegacy, From 87066e06b3cfe238b021f786896afdfd74b86303 Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Mon, 12 Jul 2021 17:25:52 +0300 Subject: [PATCH 18/31] [TSVB] Top_hit supports runtime fields (#103401) * [TSVB] Refactor top-hit aggregation to work with fields instead of _source * Allow select date strings for top_hit aggregation in table, metric, and markdown * Fix agg_with handling for top_hit and add some tests * Refactor get_agg_value and fix type check for _tsvb_chart * Refactor top_hit.js Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/aggs/top_hit.js | 37 +++++-- .../components/lib/tick_formatter.js | 2 +- .../lib/vis_data/helpers/bucket_transform.js | 2 +- .../lib/vis_data/helpers/get_agg_value.js | 4 +- .../vis_data/helpers/get_agg_value.test.js | 6 +- test/functional/apps/visualize/_tsvb_chart.ts | 103 +++++++++++++++--- .../fixtures/kbn_archiver/visualize.json | 3 +- .../page_objects/visual_builder_page.ts | 47 ++++++++ 8 files changed, 168 insertions(+), 36 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js index 546c09cdf34fd..b9ef2d8913574 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useMemo, useEffect } from 'react'; import { AggRow } from './agg_row'; import { AggSelect } from './agg_select'; import { FieldSelect } from './field_select'; @@ -62,6 +62,7 @@ const getAggWithOptions = (field = {}, fieldTypesRestriction) => { }, ]; case KBN_FIELD_TYPES.STRING: + case KBN_FIELD_TYPES.DATE: return [ { label: i18n.translate('visTypeTimeseries.topHit.aggWithOptions.concatenate', { @@ -91,16 +92,18 @@ const getOrderOptions = () => [ }, ]; +const AGG_WITH_KEY = 'agg_with'; const ORDER_DATE_RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; +const getModelDefaults = () => ({ + size: 1, + order: 'desc', + [AGG_WITH_KEY]: 'noop', +}); + const TopHitAggUi = (props) => { const { fields, series, panel } = props; - const defaults = { - size: 1, - agg_with: 'noop', - order: 'desc', - }; - const model = { ...defaults, ...props.model }; + const model = useMemo(() => ({ ...getModelDefaults(), ...props.model }), [props.model]); const indexPattern = series.override_index_pattern ? series.series_index_pattern : panel.index_pattern; @@ -110,7 +113,7 @@ const TopHitAggUi = (props) => { PANEL_TYPES.METRIC, PANEL_TYPES.MARKDOWN, ].includes(panel.type) - ? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING] + ? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING, KBN_FIELD_TYPES.DATE] : [KBN_FIELD_TYPES.NUMBER]; const handleChange = createChangeHandler(props.onChange, model); @@ -124,13 +127,23 @@ const TopHitAggUi = (props) => { const htmlId = htmlIdGenerator(); const selectedAggWithOption = aggWithOptions.find((option) => { - return model.agg_with === option.value; + return model[AGG_WITH_KEY] === option.value; }); const selectedOrderOption = orderOptions.find((option) => { return model.order === option.value; }); + useEffect(() => { + const defaultFn = aggWithOptions?.[0]?.value; + const aggWith = model[AGG_WITH_KEY]; + if (aggWith && defaultFn && aggWith !== defaultFn && !selectedAggWithOption) { + handleChange({ + [AGG_WITH_KEY]: defaultFn, + }); + } + }, [model, selectedAggWithOption, aggWithOptions, handleChange]); + return ( {
{ )} options={aggWithOptions} selectedOptions={selectedAggWithOption ? [selectedAggWithOption] : []} - onChange={handleSelectChange('agg_with')} + onChange={handleSelectChange(AGG_WITH_KEY)} singleSelection={{ asPlainText: true }} + data-test-subj="topHitAggregateWithComboBox" /> @@ -231,6 +245,7 @@ const TopHitAggUi = (props) => { onChange={handleSelectChange('order_by')} indexPattern={indexPattern} fields={fields} + data-test-subj="topHitOrderByFieldSelect" /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js index c1d82a182e509..9bccc13d19269 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js @@ -16,7 +16,7 @@ export const createTickFormatter = (format = '0,0.[00]', template, getConfig = n const fieldFormats = getFieldFormats(); if (!template) template = '{{value}}'; - const render = handlebars.compile(template, { knownHelpersOnly: true }); + const render = handlebars.compile(template, { noEscape: true, knownHelpersOnly: true }); let formatter; if (isDuration(format)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js index 16e7b9d6072cb..13b890189325c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js @@ -111,7 +111,7 @@ export const bucketTransform = { docs: { top_hits: { size: bucket.size, - _source: { includes: [bucket.field] }, + fields: [bucket.field], }, }, }, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js index 32d17ef6d6cb7..90df3f2675959 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js @@ -45,10 +45,10 @@ export const getAggValue = (row, metric) => { } const hits = get(row, [metric.id, 'docs', 'hits', 'hits'], []); - const values = hits.map((doc) => get(doc, `_source.${metric.field}`)); + const values = hits.map((doc) => doc.fields[metric.field]); const aggWith = (metric.agg_with && aggFns[metric.agg_with]) || aggFns.noop; - return aggWith(values); + return aggWith(values.flat()); case METRIC_TYPES.COUNT: return get(row, 'doc_count', null); default: diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js index a23c57f567563..ecbdd1563c304 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js @@ -67,11 +67,7 @@ describe('getAggValue', () => { doc_count: 1, docs: { hits: { - hits: [ - { _source: { example: { value: 25 } } }, - { _source: { example: { value: 25 } } }, - { _source: { example: { value: 25 } } }, - ], + hits: [{ fields: { 'example.value': [25, 25, 25] } }], }, }, }, diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index ca310493960f5..49b2ad8f9646a 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'timePicker', 'visChart', 'common', + 'settings', ]); describe('visual builder', function describeIndexTests() { @@ -44,14 +45,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('metric', () => { + const { visualBuilder } = PageObjects; + beforeEach(async () => { - await PageObjects.visualBuilder.resetPage(); - await PageObjects.visualBuilder.clickMetric(); - await PageObjects.visualBuilder.checkMetricTabIsPresent(); - await PageObjects.visualBuilder.clickPanelOptions('metric'); - await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value'); - await PageObjects.visualBuilder.setDropLastBucket(true); - await PageObjects.visualBuilder.clickDataTab('metric'); + await visualBuilder.resetPage(); + await visualBuilder.clickMetric(); + await visualBuilder.checkMetricTabIsPresent(); + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.setDropLastBucket(true); + await visualBuilder.clickDataTab('metric'); }); it('should not have inspector enabled', async () => { @@ -59,28 +62,98 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show correct data', async () => { - const value = await PageObjects.visualBuilder.getMetricValue(); + const value = await visualBuilder.getMetricValue(); expect(value).to.eql('156'); }); it('should show correct data with Math Aggregation', async () => { - await PageObjects.visualBuilder.createNewAgg(); - await PageObjects.visualBuilder.selectAggType('math', 1); - await PageObjects.visualBuilder.fillInVariable(); - await PageObjects.visualBuilder.fillInExpression('params.test + 1'); - const value = await PageObjects.visualBuilder.getMetricValue(); + await visualBuilder.createNewAgg(); + await visualBuilder.selectAggType('math', 1); + await visualBuilder.fillInVariable(); + await visualBuilder.fillInExpression('params.test + 1'); + const value = await visualBuilder.getMetricValue(); expect(value).to.eql('157'); }); it('should populate fields for basic functions', async () => { - const { visualBuilder } = PageObjects; - await visualBuilder.selectAggType('Average'); await visualBuilder.setFieldForAggregation('machine.ram'); const isFieldForAggregationValid = await visualBuilder.checkFieldForAggregationValidity(); expect(isFieldForAggregationValid).to.be(true); }); + + it('should show correct data for Value Count with Entire time range mode', async () => { + await visualBuilder.selectAggType('Value Count'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setMetricsDataTimerangeMode('Entire time range'); + + const value = await visualBuilder.getMetricValue(); + expect(value).to.eql('13,492'); + }); + + it('should show same data for kibana and string index pattern modes', async () => { + await visualBuilder.selectAggType('Max'); + await visualBuilder.setFieldForAggregation('machine.ram'); + const kibanaIndexPatternModeValue = await visualBuilder.getMetricValue(); + + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.switchIndexPatternSelectionMode(false); + const stringIndexPatternModeValue = await visualBuilder.getMetricValue(); + + expect(kibanaIndexPatternModeValue).to.eql(stringIndexPatternModeValue); + expect(kibanaIndexPatternModeValue).to.eql('32,212,254,720'); + }); + + describe('Color rules', () => { + beforeEach(async () => { + await visualBuilder.selectAggType('Min'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(0); + }); + + it('should apply color rules to visualization background', async () => { + await visualBuilder.setColorPickerValue('#FFCFDF'); + + const backGroundStyle = await visualBuilder.getBackgroundStyle(); + expect(backGroundStyle).to.eql('background-color: rgb(255, 207, 223);'); + }); + + it('should apply color rules to metric value', async () => { + await visualBuilder.setColorPickerValue('#AD7DE6', 1); + + const backGroundStyle = await visualBuilder.getMetricValueStyle(); + expect(backGroundStyle).to.eql('color: rgb(173, 125, 230);'); + }); + }); + + describe('Top Hit aggregation', () => { + beforeEach(async () => { + await visualBuilder.selectAggType('Top Hit'); + await visualBuilder.setTopHitOrderByField('@timestamp'); + }); + + it('should show correct data for string type field', async () => { + await visualBuilder.setFieldForAggregation('machine.os.raw'); + await visualBuilder.setTopHitAggregateWithOption('Concatenate'); + + const value = await visualBuilder.getMetricValue(); + expect(value).to.eql('win 7'); + }); + + it('should show correct data for runtime field', async () => { + await visualBuilder.setFieldForAggregation('hello_world_runtime_field'); + await visualBuilder.setTopHitAggregateWithOption('Concatenate'); + + const value = await visualBuilder.getMetricValue(); + expect(value).to.eql('hello world'); + }); + }); }); describe('gauge', () => { diff --git a/test/functional/fixtures/kbn_archiver/visualize.json b/test/functional/fixtures/kbn_archiver/visualize.json index 660da856964b4..225dc0592e87d 100644 --- a/test/functional/fixtures/kbn_archiver/visualize.json +++ b/test/functional/fixtures/kbn_archiver/visualize.json @@ -3,6 +3,7 @@ "fieldAttrs": "{\"utc_time\":{\"customLabel\":\"UTC time\"}}", "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}", "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "runtimeFieldMap":"{\"hello_world_runtime_field\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit('hello world')\"}}}", "timeFieldName": "@timestamp", "title": "logstash-*" }, @@ -301,4 +302,4 @@ "references": [], "type": "index-pattern", "version": "WzE1LDFd" -} \ No newline at end of file +} diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 8e28ffab6c9c3..fd89a88658b3a 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -575,6 +575,42 @@ export class VisualBuilderPageObject extends FtrService { await this.testSubjects.existOrFail('euiColorPickerPopover', { timeout: 5000 }); } + public async setColorPickerValue(colorHex: string, nth: number = 0): Promise { + const picker = await this.find.allByCssSelector('.tvbColorPicker button'); + await picker[nth].clickMouseButton(); + await this.checkColorPickerPopUpIsPresent(); + await this.find.setValue('.euiColorPicker input', colorHex); + await this.visChart.waitForVisualizationRenderingStabilized(); + } + + public async setColorRuleOperator(condition: string): Promise { + await this.retry.try(async () => { + await this.comboBox.clearInputField('colorRuleOperator'); + await this.comboBox.set('colorRuleOperator', condition); + }); + } + + public async setColorRuleValue(value: number): Promise { + await this.retry.try(async () => { + const colorRuleValueInput = await this.find.byCssSelector( + '[data-test-subj="colorRuleValue"]' + ); + await colorRuleValueInput.type(value.toString()); + }); + } + + public async getBackgroundStyle(): Promise { + await this.visChart.waitForVisualizationRenderingStabilized(); + const visualization = await this.find.byClassName('tvbVis'); + return await visualization.getAttribute('style'); + } + + public async getMetricValueStyle(): Promise { + await this.visChart.waitForVisualizationRenderingStabilized(); + const metricValue = await this.find.byCssSelector('[data-test-subj="tsvbMetricValue"]'); + return await metricValue.getAttribute('style'); + } + public async changePanelPreview(nth: number = 0): Promise { const prevRenderingCount = await this.visChart.getVisualizationRenderingCount(); const changePreviewBtnArray = await this.testSubjects.findAll('AddActivatePanelBtn'); @@ -680,4 +716,15 @@ export class VisualBuilderPageObject extends FtrService { const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode'); return await this.comboBox.isOptionSelected(dataTimeRangeMode, value); } + + public async setTopHitAggregateWithOption(option: string): Promise { + await this.comboBox.set('topHitAggregateWithComboBox', option); + } + + public async setTopHitOrderByField(timeField: string) { + await this.retry.try(async () => { + await this.comboBox.clearInputField('topHitOrderByFieldSelect'); + await this.comboBox.set('topHitOrderByFieldSelect', timeField); + }); + } } From a80791aa4ccc2902820591299ce8a89c364d2cd6 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 12 Jul 2021 10:26:41 -0400 Subject: [PATCH 19/31] Pass locale to calendar (#105134) --- .../components/analytics/components/analytics_filters.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx index 0c8455e986ae1..dd99d368a0105 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx @@ -80,6 +80,7 @@ export const AnalyticsFilters: React.FC = () => { 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel', { defaultMessage: 'Filter by start date' } )} + locale={i18n.getLocale()} /> } endDateControl={ @@ -93,6 +94,7 @@ export const AnalyticsFilters: React.FC = () => { 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel', { defaultMessage: 'Filter by end date' } )} + locale={i18n.getLocale()} /> } fullWidth From d6a36926008b000d006d012edafed5cad6a7f3f6 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Mon, 12 Jul 2021 09:58:39 -0500 Subject: [PATCH 20/31] [DOCS] Adds how to create dashboard drilldowns for Top N and Table TSVB panels (#104548) --- docs/user/dashboard/tsvb.asciidoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index 89da3f7285924..11fe71b7639bb 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -148,6 +148,27 @@ The *Markdown* visualization supports Markdown with Handlebar (mustache) syntax For answers to frequently asked *TSVB* question, review the following. +[float] +===== How do I create dashboard drilldowns for Top N and Table visualizations? + +You can create dashboard drilldowns that include the specified time range for *Top N* and *Table* visualizations. + +. Open the dashboard that you want to link to, then copy the URL. + +. Open the dashboard with the *Top N* and *Table* visualization panel, then click *Edit* in the toolbar. + +. Open the *Top N* or *Table* panel menu, then select *Edit visualization*. + +. Click *Panel options*. + +. In the *Item URL* field, enter the URL. ++ +For example `dashboards#/view/f193ca90-c9f4-11eb-b038-dd3270053a27`. + +. Click *Save and return*. + +. In the toolbar, cick *Save as*, then make sure *Store time with dashboard* is deselected. + [float] ===== Why is my TSVB visualization missing data? From 1aa9459ba2d8c4e736fdae5c0f4f4a59f09c7973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Mon, 12 Jul 2021 11:07:34 -0400 Subject: [PATCH 21/31] [CTI] converts disabled panel danger to warning (#104989) --- .../overview_cti_links/cti_disabled_module.tsx | 4 ++-- .../overview_cti_links/cti_inner_panel.tsx | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx index 21a4beca72f3b..1600356882c36 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx @@ -21,11 +21,11 @@ export const CtiDisabledModuleComponent = () => { const danger = useMemo( () => ( + {i18n.DANGER_BUTTON} } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx index 08bf0a432f9bb..ddff78608dfb0 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx @@ -17,13 +17,9 @@ const ButtonContainer = styled(EuiFlexGroup)` padding: ${({ theme }) => theme.eui.paddingSizes.s}; `; -const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' | 'danger' }>` +const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' }>` color: ${({ theme, textcolor }) => - textcolor === 'primary' - ? theme.eui.euiColorPrimary - : textcolor === 'warning' - ? theme.eui.euiColorWarningText - : theme.eui.euiColorDangerText}; + textcolor === 'primary' ? theme.eui.euiColorPrimary : theme.eui.euiColorWarningText}; margin-bottom: ${({ theme }) => theme.eui.paddingSizes.m}; `; @@ -40,12 +36,12 @@ export const CtiInnerPanel = ({ body, button, }: { - color: 'primary' | 'warning' | 'danger'; + color: 'primary' | 'warning'; title: string; body: string; button?: JSX.Element; }) => { - const iconType = color === 'primary' ? 'iInCircle' : color === 'warning' ? 'help' : 'alert'; + const iconType = color === 'primary' ? 'iInCircle' : 'help'; return ( From 0c9777c6020501b6c953b4bca97f41dd858549f1 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 12 Jul 2021 11:23:57 -0400 Subject: [PATCH 22/31] [Uptime] Refactor page headers to avoid invalid markup (#104215) * Refactor monitor and waterfall page headers to avoid rendering invalid markup. * Update tests. * Translate page titles. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/monitor/monitor_title.test.tsx | 37 ++++++++++------- .../components/monitor/monitor_title.tsx | 41 +++++++++---------- .../step_detail/step_detail_container.tsx | 9 ++-- .../step_detail/step_page_title.tsx | 13 ++---- x-pack/plugins/uptime/public/routes.tsx | 33 +++++++++++---- .../uptime/server/lib/requests/get_certs.ts | 1 - 6 files changed, 77 insertions(+), 57 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx index 4fd6335c3d3ca..726ad235f7f49 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx @@ -11,7 +11,7 @@ import { screen } from '@testing-library/react'; import { render } from '../../lib/helper/rtl_helpers'; import * as reactRouterDom from 'react-router-dom'; import { Ping } from '../../../common/runtime_types'; -import { MonitorPageTitle } from './monitor_title'; +import { MonitorPageTitle, MonitorPageTitleContent } from './monitor_title'; jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -77,11 +77,17 @@ describe('MonitorTitle component', () => { }); it('renders the monitor heading and EnableMonitorAlert toggle', () => { - render(, { - state: { monitorStatus: { status: monitorStatusWithName, loading: false } }, - }); - expect(screen.getByRole('heading', { level: 1, name: monitorName })).toBeInTheDocument(); - expect(screen.getByTestId('uptimeDisplayDefineConnector')).toBeInTheDocument(); + render( + <> + + + , + { + state: { monitorStatus: { status: monitorStatusWithName, loading: false } }, + } + ); + expect(screen.getByText(monitorName)); + expect(screen.getByRole('switch')).toBeInTheDocument(); }); it('renders the user provided monitorId when the name is not present', () => { @@ -89,21 +95,24 @@ describe('MonitorTitle component', () => { render(, { state: { monitorStatus: { status: defaultMonitorStatus, loading: false } }, }); - expect(screen.getByRole('heading', { level: 1, name: defaultMonitorId })).toBeInTheDocument(); + expect(screen.getByText(defaultMonitorId)); }); it('renders the url when the monitorId is auto generated and the monitor name is not present', () => { mockReactRouterDomHooks({ useParamsResponse: { monitorId: autoGeneratedMonitorIdEncoded } }); - render(, { - state: { monitorStatus: { status: defaultMonitorStatus, loading: false } }, - }); - expect( - screen.getByRole('heading', { level: 1, name: defaultMonitorStatus.url?.full }) - ).toBeInTheDocument(); + render( +
+ +
, + { + state: { monitorStatus: { status: defaultMonitorStatus, loading: false } }, + } + ); + expect(screen.getByText(defaultMonitorStatus!.url!.full!)); }); it('renders beta disclaimer for synthetics monitors', () => { - render(, { + render(, { state: { monitorStatus: { status: defaultBrowserMonitorStatus, loading: false } }, }); const betaLink = screen.getByRole('link', { diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx index 2112af0653669..aa68e2aa7fc4b 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx @@ -5,15 +5,7 @@ * 2.0. */ -import { - EuiBadge, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, - EuiLink, - EuiText, -} from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { useSelector } from 'react-redux'; @@ -40,18 +32,11 @@ const getPageTitle = (monitorId: string, selectedMonitor: Ping | null) => { return monitorId; }; -export const MonitorPageTitle: React.FC = () => { +export const MonitorPageTitleContent: React.FC = () => { const monitorId = useMonitorId(); - const selectedMonitor = useSelector(monitorStatusSelector); - - const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor); - const type = selectedMonitor?.monitor?.type; const isBrowser = type === 'browser'; - - useBreadcrumbs([{ text: nameOrId }]); - const renderMonitorType = (monitorType: string) => { switch (monitorType) { case 'http': @@ -86,12 +71,13 @@ export const MonitorPageTitle: React.FC = () => { return ''; } }; - return ( <> - -

{nameOrId}

-
+ + + + + @@ -118,7 +104,18 @@ export const MonitorPageTitle: React.FC = () => { )} - ); }; + +export const MonitorPageTitle: React.FC = () => { + const monitorId = useMonitorId(); + + const selectedMonitor = useSelector(monitorStatusSelector); + + const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor); + + useBreadcrumbs([{ text: nameOrId }]); + + return {nameOrId}; +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx index 610107f406306..c24ecd9183865 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx @@ -16,7 +16,7 @@ import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/ import { useMonitorBreadcrumb } from './use_monitor_breadcrumb'; import { ClientPluginsStart } from '../../../../apps/plugin'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { StepPageTitle } from './step_page_title'; +import { StepPageTitleContent } from './step_page_title'; import { StepPageNavigation } from './step_page_nav'; import { WaterfallChartContainer } from './waterfall/waterfall_chart_container'; @@ -78,10 +78,11 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) return ( void; handleNextStep: () => void; } -export const StepPageTitle = ({ - stepName, + +export const StepPageTitleContent = ({ stepIndex, totalSteps, handleNextStep, @@ -29,11 +29,6 @@ export const StepPageTitle = ({ }: Props) => { return ( - - -

{stepName}

-
-
diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index e3c558cee2c32..2b0cc4dc5e5c2 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -23,7 +23,7 @@ import { UptimePage, useUptimeTelemetry } from './hooks'; import { OverviewPageComponent } from './pages/overview'; import { SyntheticsCheckSteps } from './pages/synthetics/synthetics_checks'; import { ClientPluginsStart } from './apps/plugin'; -import { MonitorPageTitle } from './components/monitor/monitor_title'; +import { MonitorPageTitle, MonitorPageTitleContent } from './components/monitor/monitor_title'; import { UptimeDatePicker } from './components/common/uptime_date_picker'; import { useKibana } from '../../../../src/plugins/kibana_react/public'; import { CertRefreshBtn } from './components/certificates/cert_refresh_btn'; @@ -36,10 +36,16 @@ interface RouteProps { dataTestSubj: string; title: string; telemetryId: UptimePage; - pageHeader?: { pageTitle: string | JSX.Element; rightSideItems?: JSX.Element[] }; + pageHeader?: { + children?: JSX.Element; + pageTitle: string | JSX.Element; + rightSideItems?: JSX.Element[]; + }; } -const baseTitle = 'Uptime - Kibana'; +const baseTitle = i18n.translate('xpack.uptime.routes.baseTitle', { + defaultMessage: 'Uptime - Kibana', +}); export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.heading', { defaultMessage: 'Monitors', @@ -47,18 +53,25 @@ export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.h const Routes: RouteProps[] = [ { - title: `Monitor | ${baseTitle}`, + title: i18n.translate('xpack.uptime.monitorRoute.title', { + defaultMessage: 'Monitor | {baseTitle}', + values: { baseTitle }, + }), path: MONITOR_ROUTE, component: MonitorPage, dataTestSubj: 'uptimeMonitorPage', telemetryId: UptimePage.Monitor, pageHeader: { + children: , pageTitle: , rightSideItems: [], }, }, { - title: `Settings | ${baseTitle}`, + title: i18n.translate('xpack.uptime.settingsRoute.title', { + defaultMessage: `Settings | {baseTitle}`, + values: { baseTitle }, + }), path: SETTINGS_ROUTE, component: SettingsPage, dataTestSubj: 'uptimeSettingsPage', @@ -70,7 +83,10 @@ const Routes: RouteProps[] = [ }, }, { - title: `Certificates | ${baseTitle}`, + title: i18n.translate('xpack.uptime.certificatesRoute.title', { + defaultMessage: `Certificates | {baseTitle}`, + values: { baseTitle }, + }), path: CERTIFICATES_ROUTE, component: CertificatesPage, dataTestSubj: 'uptimeCertificatesPage', @@ -81,7 +97,10 @@ const Routes: RouteProps[] = [ }, }, { - title: baseTitle, + title: i18n.translate('xpack.uptime.stepDetailRoute.title', { + defaultMessage: 'Synthetics detail | {baseTitle}', + values: { baseTitle }, + }), path: STEP_DETAIL_ROUTE, component: StepDetailPage, dataTestSubj: 'uptimeStepDetailPage', diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 1b20ed9085fef..7639484f51737 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -138,7 +138,6 @@ export const getCerts: UMElasticsearchQueryFn = asyn searchBody.query.bool.filter.push(validityFilters); } - // console.log(JSON.stringify(params, null, 2)); const { body: result } = await uptimeEsClient.search({ body: searchBody, }); From 3e5ed774700336657f25e8f0aef10e3bf17a93a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 12 Jul 2021 18:55:06 +0300 Subject: [PATCH 23/31] [Osquery] Fix 7.14 live query history view (#105211) --- .../action_results/action_results_summary.tsx | 22 +------ .../osquery/public/actions/actions_table.tsx | 19 ++++++ .../public/agents/agent_id_to_name.tsx | 37 ++++++++++++ .../osquery/public/agents/agents_table.tsx | 24 ++++++-- .../public/agents/use_agent_details.ts | 36 +++++++++++ .../osquery/public/agents/use_all_agents.ts | 2 +- .../public/live_queries/form/index.tsx | 2 +- .../public/routes/live_queries/new/index.tsx | 10 +++- .../routes/saved_queries/list/index.tsx | 60 ++++++++++--------- .../saved_queries/saved_queries_dropdown.tsx | 41 +++++++++---- .../scheduled_query_group_queries_table.tsx | 2 +- .../actions/all/query.all_actions.dsl.ts | 23 +++++-- 12 files changed, 207 insertions(+), 71 deletions(-) create mode 100644 x-pack/plugins/osquery/public/agents/agent_id_to_name.tsx create mode 100644 x-pack/plugins/osquery/public/agents/use_agent_details.ts diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx index d3b0e38a5e033..bf4c97d63d74c 100644 --- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx @@ -8,15 +8,13 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { i18n } from '@kbn/i18n'; -import { EuiLink, EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui'; +import { EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { PLUGIN_ID } from '../../../fleet/common'; -import { pagePathGetters } from '../../../fleet/public'; +import { AgentIdToName } from '../agents/agent_id_to_name'; import { useActionResults } from './use_action_results'; import { useAllResults } from '../results/use_all_results'; import { Direction } from '../../common/search_strategy'; -import { useKibana } from '../common/lib/kibana'; interface ActionResultsSummaryProps { actionId: string; @@ -35,7 +33,6 @@ const ActionResultsSummaryComponent: React.FC = ({ expirationDate, agentIds, }) => { - const getUrlForApp = useKibana().services.application.getUrlForApp; // @ts-expect-error update types const [pageIndex, setPageIndex] = useState(0); // @ts-expect-error update types @@ -70,20 +67,7 @@ const ActionResultsSummaryComponent: React.FC = ({ isLive, }); - const renderAgentIdColumn = useCallback( - (agentId) => ( - - {agentId} - - ), - [getUrlForApp] - ); + const renderAgentIdColumn = useCallback((agentId) => , []); const renderRowsColumn = useCallback( (_, item) => { diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx index 0ee928ad8aa14..045c1f67b070d 100644 --- a/x-pack/plugins/osquery/public/actions/actions_table.tsx +++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx @@ -9,6 +9,7 @@ import { isArray } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiButtonIcon, EuiCodeBlock, formatDate } from '@elastic/eui'; import React, { useState, useCallback, useMemo } from 'react'; +import { useHistory } from 'react-router-dom'; import { useAllActions } from './use_all_actions'; import { Direction } from '../../common/search_strategy'; @@ -27,6 +28,7 @@ const ActionTableResultsButton = React.memo(({ ac ActionTableResultsButton.displayName = 'ActionTableResultsButton'; const ActionsTableComponent = () => { + const { push } = useHistory(); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(20); @@ -67,6 +69,16 @@ const ActionsTableComponent = () => { [] ); + const handlePlayClick = useCallback( + (item) => + push('/live_queries/new', { + form: { + query: item._source?.data?.query, + }, + }), + [push] + ); + const columns = useMemo( () => [ { @@ -106,6 +118,11 @@ const ActionsTableComponent = () => { defaultMessage: 'View details', }), actions: [ + { + type: 'icon', + icon: 'play', + onClick: handlePlayClick, + }, { render: renderActionsColumn, }, @@ -113,6 +130,7 @@ const ActionsTableComponent = () => { }, ], [ + handlePlayClick, renderActionsColumn, renderAgentsColumn, renderCreatedByColumn, @@ -135,6 +153,7 @@ const ActionsTableComponent = () => { = ({ agentId }) => { + const getUrlForApp = useKibana().services.application.getUrlForApp; + const { data } = useAgentDetails({ agentId }); + + return ( + + {data?.item.local_metadata.host.name ?? agentId} + + ); +}; + +export const AgentIdToName = React.memo(AgentIdToNameComponent); diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 7e8f49c051614..53e2ce1d53420 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -21,7 +21,12 @@ import { generateAgentSelection, } from './helpers'; -import { SELECT_AGENT_LABEL, generateSelectedAgentsMessage } from './translations'; +import { + SELECT_AGENT_LABEL, + generateSelectedAgentsMessage, + ALL_AGENTS_LABEL, + AGENT_POLICY_LABEL, +} from './translations'; import { AGENT_GROUP_KEY, @@ -72,8 +77,17 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh useEffect(() => { if (agentSelection && !defaultValueInitialized.current && options.length) { - if (agentSelection.policiesSelected) { - const policyOptions = find(['label', 'Policy'], options); + if (agentSelection.allAgentsSelected) { + const allAgentsOptions = find(['label', ALL_AGENTS_LABEL], options); + + if (allAgentsOptions?.options) { + setSelectedOptions(allAgentsOptions.options); + defaultValueInitialized.current = true; + } + } + + if (agentSelection.policiesSelected.length) { + const policyOptions = find(['label', AGENT_POLICY_LABEL], options); if (policyOptions) { const defaultOptions = policyOptions.options?.filter((option) => @@ -82,12 +96,12 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh if (defaultOptions?.length) { setSelectedOptions(defaultOptions); + defaultValueInitialized.current = true; } - defaultValueInitialized.current = true; } } } - }, [agentSelection, options]); + }, [agentSelection, options, selectedOptions]); useEffect(() => { // update the groups when groups or agents have changed diff --git a/x-pack/plugins/osquery/public/agents/use_agent_details.ts b/x-pack/plugins/osquery/public/agents/use_agent_details.ts new file mode 100644 index 0000000000000..1a0663812dec3 --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/use_agent_details.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useQuery } from 'react-query'; + +import { GetOneAgentResponse, agentRouteService } from '../../../fleet/common'; +import { useErrorToast } from '../common/hooks/use_error_toast'; +import { useKibana } from '../common/lib/kibana'; + +interface UseAgentDetails { + agentId: string; +} + +export const useAgentDetails = ({ agentId }: UseAgentDetails) => { + const { http } = useKibana().services; + const setErrorToast = useErrorToast(); + return useQuery( + ['agentDetails', agentId], + () => http.get(agentRouteService.getInfoPath(agentId)), + { + enabled: agentId.length > 0, + onSuccess: () => setErrorToast(), + onError: (error) => + setErrorToast(error as Error, { + title: i18n.translate('xpack.osquery.agentDetails.fetchError', { + defaultMessage: 'Error while fetching agent details', + }), + }), + } + ); +}; diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts index 30ba4d2f57907..cda15cc805437 100644 --- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -38,7 +38,7 @@ export const useAllAgents = ( let kuery = `last_checkin_status: online and (${policyFragment})`; if (searchValue) { - kuery += `and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`; + kuery += ` and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`; } return http.get(agentRouteService.getListPath(), { diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 8654a74fecfb4..658280042696e 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -110,7 +110,7 @@ const LiveQueryFormComponent: React.FC = ({ { agentSelection: { agents: [], - allAgentsSelected: false, + allAgentsSelected: true, platformsSelected: [], policiesSelected: [], }, diff --git a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx index 9967eb97cddf2..40614c1f3e1b8 100644 --- a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx +++ b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx @@ -8,7 +8,7 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import qs from 'query-string'; import { WithHeaderLayout } from '../../../components/layouts'; @@ -19,12 +19,18 @@ import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge'; const NewLiveQueryPageComponent = () => { useBreadcrumbs('live_query_new'); + const { replace } = useHistory(); const location = useLocation(); const liveQueryListProps = useRouterNavigate('live_queries'); const formDefaultValue = useMemo(() => { const queryParams = qs.parse(location.search); + if (location.state?.form.query) { + replace({ state: null }); + return { query: location.state?.form.query }; + } + if (queryParams?.agentPolicyId) { return { agentSelection: { @@ -37,7 +43,7 @@ const NewLiveQueryPageComponent = () => { } return undefined; - }, [location.search]); + }, [location.search, location.state, replace]); const LeftColumn = useMemo( () => ( diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx index 7e8e8e543dfab..8738c06d06597 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx @@ -16,6 +16,7 @@ import { import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useHistory } from 'react-router-dom'; import { SavedObject } from 'kibana/public'; import { WithHeaderLayout } from '../../../components/layouts'; @@ -51,6 +52,7 @@ const EditButton = React.memo(EditButtonComponent); const SavedQueriesPageComponent = () => { useBreadcrumbs('saved_queries'); + const { push } = useHistory(); const newQueryLinkProps = useRouterNavigate('saved_queries/new'); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -59,21 +61,15 @@ const SavedQueriesPageComponent = () => { const { data } = useSavedQueries({ isLive: true }); - // const handlePlayClick = useCallback( - // (item) => - // push({ - // search: qs.stringify({ - // tab: 'live_query', - // }), - // state: { - // query: { - // id: item.id, - // query: item.attributes.query, - // }, - // }, - // }), - // [push] - // ); + const handlePlayClick = useCallback( + (item) => + push('/live_queries/new', { + form: { + savedQueryId: item.id, + }, + }), + [push] + ); const renderEditAction = useCallback( (item: SavedObject<{ name: string }>) => ( @@ -96,45 +92,53 @@ const SavedQueriesPageComponent = () => { () => [ { field: 'attributes.id', - name: 'Query ID', + name: i18n.translate('xpack.osquery.savedQueries.table.queryIdColumnTitle', { + defaultMessage: 'Query ID', + }), sortable: true, truncateText: true, }, { field: 'attributes.description', - name: 'Description', + name: i18n.translate('xpack.osquery.savedQueries.table.descriptionColumnTitle', { + defaultMessage: 'Description', + }), sortable: true, truncateText: true, }, { field: 'attributes.created_by', - name: 'Created by', + name: i18n.translate('xpack.osquery.savedQueries.table.createdByColumnTitle', { + defaultMessage: 'Created by', + }), sortable: true, truncateText: true, }, { field: 'attributes.updated_at', - name: 'Last updated at', + name: i18n.translate('xpack.osquery.savedQueries.table.updatedAtColumnTitle', { + defaultMessage: 'Last updated at', + }), sortable: (item: SavedObject<{ updated_at: string }>) => item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0, truncateText: true, render: renderUpdatedAt, }, { - name: 'Actions', + name: i18n.translate('xpack.osquery.savedQueries.table.actionsColumnTitle', { + defaultMessage: 'Actions', + }), actions: [ - // { - // name: 'Live query', - // description: 'Run live query', - // type: 'icon', - // icon: 'play', - // onClick: handlePlayClick, - // }, + { + type: 'icon', + icon: 'play', + onClick: handlePlayClick, + }, { render: renderEditAction }, ], }, ], - [renderEditAction, renderUpdatedAt] + [handlePlayClick, renderEditAction, renderUpdatedAt] ); const onTableChange = useCallback(({ page = {}, sort = {} }) => { diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx index e30954a695b2d..073b56bfd1d4c 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx @@ -7,10 +7,11 @@ import { find } from 'lodash/fp'; import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiText } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { SimpleSavedObject } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useHistory, useLocation } from 'react-router-dom'; import { useSavedQueries } from './use_saved_queries'; @@ -29,19 +30,25 @@ const SavedQueriesDropdownComponent: React.FC = ({ disabled, onChange, }) => { + const { replace } = useHistory(); + const location = useLocation(); const [selectedOptions, setSelectedOptions] = useState([]); const { data } = useSavedQueries({}); - const queryOptions = - data?.savedObjects?.map((savedQuery) => ({ - label: savedQuery.attributes.id ?? '', - value: { - id: savedQuery.attributes.id, - description: savedQuery.attributes.description, - query: savedQuery.attributes.query, - }, - })) ?? []; + const queryOptions = useMemo( + () => + data?.savedObjects?.map((savedQuery) => ({ + label: savedQuery.attributes.id ?? '', + value: { + savedObjectId: savedQuery.id, + id: savedQuery.attributes.id, + description: savedQuery.attributes.description, + query: savedQuery.attributes.query, + }, + })) ?? [], + [data?.savedObjects] + ); const handleSavedQueryChange = useCallback( (newSelectedOptions) => { @@ -73,6 +80,20 @@ const SavedQueriesDropdownComponent: React.FC = ({ [] ); + useEffect(() => { + const savedQueryId = location.state?.form?.savedQueryId; + + if (savedQueryId) { + const savedQueryOption = find(['value.savedObjectId', savedQueryId], queryOptions); + + if (savedQueryOption) { + handleSavedQueryChange([savedQueryOption]); + } + + replace({ state: null }); + } + }, [handleSavedQueryChange, replace, location.state, queryOptions]); + return ( Date: Mon, 12 Jul 2021 11:56:43 -0400 Subject: [PATCH 24/31] [Fleet] Fix enrollment flyout with fleet server from policy page (#104542) * [Fleet] Fix enrollment flyout with fleet server from policy page * Fix tests * Show enrollment instructions for add agent from fleet server policy * Fix conditions to show fleet server setup in flyout Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../agent_enrollment_flyout.test.mocks.ts | 1 + .../agent_enrollment_flyout.test.tsx | 6 ++- .../agent_enrollment_flyout/index.tsx | 39 ++++++++++++-- .../managed_instructions.tsx | 54 ++++++++++++------- .../agent_enrollment_flyout/steps.tsx | 22 ++------ .../agent_enrollment_flyout/types.ts | 8 ++- x-pack/plugins/fleet/public/types/index.ts | 1 + 7 files changed, 87 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts index d2e7c4089e88b..5c292187982dc 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts @@ -11,6 +11,7 @@ jest.mock('../../hooks/use_request', () => { ...module, useGetSettings: jest.fn(), sendGetFleetStatus: jest.fn(), + sendGetOneAgentPolicy: jest.fn(), }; }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index f68b1b878c51c..18296134ee1a7 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -16,7 +16,7 @@ import { coreMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import type { AgentPolicy } from '../../../common'; -import { useGetSettings, sendGetFleetStatus } from '../../hooks/use_request'; +import { useGetSettings, sendGetFleetStatus, sendGetOneAgentPolicy } from '../../hooks/use_request'; import { FleetStatusProvider, ConfigContext } from '../../hooks'; import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components'; @@ -79,6 +79,10 @@ describe('', () => { data: { isReady: true }, }); + (sendGetOneAgentPolicy as jest.Mock).mockResolvedValue({ + data: { item: { package_policies: [] } }, + }); + (useFleetServerInstructions as jest.Mock).mockReturnValue({ serviceToken: 'test', getServiceToken: jest.fn(), diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx index 9b82b2a80b5e1..87911e5d6c2c7 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx @@ -22,7 +22,9 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useGetSettings, useUrlModal } from '../../hooks'; +import { useGetSettings, useUrlModal, sendGetOneAgentPolicy, useFleetStatus } from '../../hooks'; +import { FLEET_SERVER_PACKAGE } from '../../constants'; +import type { PackagePolicy } from '../../types'; import { ManagedInstructions } from './managed_instructions'; import { StandaloneInstructions } from './standalone_instructions'; @@ -63,6 +65,30 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ } }, [modal, lastModal, settings]); + const fleetStatus = useFleetStatus(); + const [policyId, setSelectedPolicyId] = useState(agentPolicy?.id); + const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false); + + useEffect(() => { + async function checkPolicyIsFleetServer() { + if (policyId && setIsFleetServerPolicySelected) { + const agentPolicyRequest = await sendGetOneAgentPolicy(policyId); + if ( + agentPolicyRequest.data?.item && + (agentPolicyRequest.data.item.package_policies as PackagePolicy[]).some( + (packagePolicy) => packagePolicy.package?.name === FLEET_SERVER_PACKAGE + ) + ) { + setIsFleetServerPolicySelected(true); + } else { + setIsFleetServerPolicySelected(false); + } + } + } + + checkPolicyIsFleetServer(); + }, [policyId]); + const isLoadingInitialRequest = settings.isLoading && settings.isInitialRequest; return ( @@ -110,16 +136,23 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ ) : undefined } > - {fleetServerHosts.length === 0 && mode === 'managed' ? null : mode === 'managed' ? ( + {mode === 'managed' ? ( ) : ( diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx index 61f86335cd7f9..8054c48fbbaa8 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx @@ -11,7 +11,7 @@ import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/st import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useGetOneEnrollmentAPIKey, useGetSettings, useLink, useFleetStatus } from '../../hooks'; +import { useGetOneEnrollmentAPIKey, useLink, useFleetStatus } from '../../hooks'; import { ManualInstructions } from '../../components/enrollment_instructions'; import { @@ -56,14 +56,19 @@ const FleetServerMissingRequirements = () => { }; export const ManagedInstructions = React.memo( - ({ agentPolicy, agentPolicies, viewDataStep }) => { + ({ + agentPolicy, + agentPolicies, + viewDataStep, + setSelectedPolicyId, + isFleetServerPolicySelected, + settings, + }) => { const fleetStatus = useFleetStatus(); const [selectedApiKeyId, setSelectedAPIKeyId] = useState(); - const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false); const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId); - const settings = useGetSettings(); const fleetServerInstructions = useFleetServerInstructions(apiKey?.data?.item?.policy_id); const fleetServerSteps = useMemo(() => { @@ -88,7 +93,7 @@ export const ManagedInstructions = React.memo( }, [fleetServerInstructions]); const steps = useMemo(() => { - const fleetServerHosts = settings.data?.item?.fleet_server_hosts || []; + const fleetServerHosts = settings?.fleet_server_hosts || []; const baseSteps: EuiContainedStepProps[] = [ DownloadStep(), !agentPolicy @@ -96,7 +101,7 @@ export const ManagedInstructions = React.memo( agentPolicies, selectedApiKeyId, setSelectedAPIKeyId, - setIsFleetServerPolicySelected, + setSelectedPolicyId, }) : AgentEnrollmentKeySelectionStep({ agentPolicy, selectedApiKeyId, setSelectedAPIKeyId }), ]; @@ -121,30 +126,39 @@ export const ManagedInstructions = React.memo( }, [ agentPolicy, selectedApiKeyId, + setSelectedPolicyId, setSelectedAPIKeyId, agentPolicies, apiKey.data, fleetServerSteps, isFleetServerPolicySelected, - settings.data?.item?.fleet_server_hosts, + settings?.fleet_server_hosts, viewDataStep, ]); + if (fleetStatus.isReady && settings?.fleet_server_hosts.length === 0) { + return null; + } + + if (fleetStatus.isReady) { + return ( + <> + + + + + + + ); + } + return ( <> - {fleetStatus.isReady ? ( - <> - - - - - - - ) : fleetStatus.missingRequirements?.length === 1 && - fleetStatus.missingRequirements[0] === 'fleet_server' ? ( + {fleetStatus.missingRequirements?.length === 1 && + fleetStatus.missingRequirements[0] === 'fleet_server' ? ( ) : ( diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx index 6cffa39628d92..1cfdc45fb7dba 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx @@ -11,9 +11,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import semver from 'semver'; -import type { AgentPolicy, PackagePolicy } from '../../types'; -import { sendGetOneAgentPolicy, useKibanaVersion } from '../../hooks'; -import { FLEET_SERVER_PACKAGE } from '../../constants'; +import type { AgentPolicy } from '../../types'; +import { useKibanaVersion } from '../../hooks'; import { EnrollmentStepAgentPolicy } from './agent_policy_selection'; import { AdvancedAgentAuthenticationSettings } from './advanced_agent_authentication_settings'; @@ -69,13 +68,11 @@ export const AgentPolicySelectionStep = ({ selectedApiKeyId, setSelectedAPIKeyId, excludeFleetServer, - setIsFleetServerPolicySelected, }: { agentPolicies?: AgentPolicy[]; setSelectedPolicyId?: (policyId?: string) => void; selectedApiKeyId?: string; setSelectedAPIKeyId?: (key?: string) => void; - setIsFleetServerPolicySelected?: (selected: boolean) => void; excludeFleetServer?: boolean; }) => { const regularAgentPolicies = useMemo(() => { @@ -92,21 +89,8 @@ export const AgentPolicySelectionStep = ({ if (setSelectedPolicyId) { setSelectedPolicyId(policyId); } - if (policyId && setIsFleetServerPolicySelected) { - const agentPolicyRequest = await sendGetOneAgentPolicy(policyId); - if ( - agentPolicyRequest.data?.item && - (agentPolicyRequest.data.item.package_policies as PackagePolicy[]).some( - (packagePolicy) => packagePolicy.package?.name === FLEET_SERVER_PACKAGE - ) - ) { - setIsFleetServerPolicySelected(true); - } else { - setIsFleetServerPolicySelected(false); - } - } }, - [setIsFleetServerPolicySelected, setSelectedPolicyId] + [setSelectedPolicyId] ); return { diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts index 9ee514c634655..282a5b243caed 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts @@ -7,7 +7,7 @@ import type { EuiStepProps } from '@elastic/eui'; -import type { AgentPolicy } from '../../types'; +import type { AgentPolicy, Settings } from '../../types'; export interface BaseProps { /** @@ -27,4 +27,10 @@ export interface BaseProps { * in some way. This is an area for consumers to render a button and text explaining how data can be viewed. */ viewDataStep?: EuiStepProps; + + settings?: Settings; + + setSelectedPolicyId?: (policyId?: string) => void; + + isFleetServerPolicySelected?: boolean; } diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index f21552d68e77b..c91ec42d3e527 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -27,6 +27,7 @@ export { PackagePolicyPackage, Output, DataStream, + Settings, // API schema - misc setup, status GetFleetStatusResponse, // API schemas - Agent policy From 42c743be8848e08761f76903c4aa1f0b5f8e899a Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Mon, 12 Jul 2021 12:50:41 -0400 Subject: [PATCH 25/31] [Top Menu] Increase size of top menu links to `s` (#103144) * Increased non-emphasized header links size from `xs` to `s` * [Observability] Updating header links to use EuiHeaderLink * [Spaces Menu] Larger spinner * [Help Menu] Increase size of links * [Canvas] Increase size to `s` --- .../chrome/ui/header/header_help_menu.tsx | 12 +-- .../data/public/ui/filter_bar/filter_bar.tsx | 2 +- .../index_pattern_table.tsx | 2 +- .../public/top_nav_menu/top_nav_menu_item.tsx | 2 +- .../app/RumDashboard/ActionMenu/index.tsx | 60 ++++++-------- .../alerting_popover_flyout.tsx | 1 - .../anomaly_detection_setup_link.tsx | 1 - .../shared/apm_header_action_menu/index.tsx | 2 +- .../function_reference_generator.tsx | 2 +- .../public/components/help_menu/help_menu.tsx | 4 +- .../__snapshots__/edit_menu.stories.storyshot | 12 +-- .../edit_menu/edit_menu.component.tsx | 2 +- .../share_menu.stories.storyshot | 4 +- .../share_menu/share_menu.component.tsx | 2 +- .../__snapshots__/view_menu.stories.storyshot | 8 +- .../view_menu/view_menu.component.tsx | 2 +- .../file_error_callouts.tsx | 2 +- .../components/metrics_alert_dropdown.tsx | 7 +- .../components/alert_dropdown.tsx | 7 +- .../infra/public/pages/logs/page_content.tsx | 37 ++++----- .../infra/public/pages/metrics/index.tsx | 43 ++++------ .../anomaly_detection_flyout.tsx | 7 +- .../nav_control/nav_control_popover.tsx | 2 +- .../common/header/action_menu_content.tsx | 82 +++++++++---------- .../alerts/toggle_alert_flyout_button.tsx | 7 +- .../overview/synthetics_callout.test.tsx | 6 -- .../overview/synthetics_callout.tsx | 10 +-- 27 files changed, 134 insertions(+), 194 deletions(-) diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx index c6a09c1177a5e..cbf89bba2ca44 100644 --- a/src/core/public/chrome/ui/header/header_help_menu.tsx +++ b/src/core/public/chrome/ui/header/header_help_menu.tsx @@ -211,7 +211,7 @@ export class HeaderHelpMenu extends Component { return ( - + { - + { - + { @@ -330,7 +330,7 @@ export class HeaderHelpMenu extends Component { {customLinks} {content && ( <> - {customLinks && } + {customLinks && } )} @@ -383,7 +383,7 @@ const createCustomLink = ( ) => { return ( - + {text} {addSpacer && } 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 4655bbf8e91a5..cc796ad749f0b 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -77,7 +77,7 @@ function FilterBarUI(props: Props) { const button = ( setIsAddFilterPopoverOpen(true)} data-test-subj="addFilter" className="globalFilterBar__addButton" diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx index 6bd06528084ce..6405a81282471 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx @@ -132,7 +132,7 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { } ) => ( <> - + {name}   diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx index 523bf07f828c9..f4ca53a9a974e 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx @@ -42,7 +42,7 @@ export function TopNavMenuItem(props: TopNavMenuData) { {upperFirst(props.label || props.id!)} ) : ( - + {upperFirst(props.label || props.id!)} ); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx index 63ba7047696ca..4e6544a20f301 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx @@ -6,12 +6,7 @@ */ import React from 'react'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiToolTip, -} from '@elastic/eui'; +import { EuiHeaderLinks, EuiHeaderLink, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { createExploratoryViewUrl, @@ -62,38 +57,29 @@ export function UXActionMenu({ - - - {ANALYZE_MESSAGE}

}> - - {ANALYZE_DATA} - -
-
- - + {ANALYZE_MESSAGE}

}> + - {i18n.translate('xpack.apm.addDataButtonLabel', { - defaultMessage: 'Add data', - })} -
-
-
+ {ANALYZE_DATA} +
+ + + {i18n.translate('xpack.apm.addDataButtonLabel', { + defaultMessage: 'Add data', + })} + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx index ca73f6ddd05b3..4abd36a277311 100644 --- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx +++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx @@ -66,7 +66,6 @@ export function AlertingPopoverAndFlyout({ const button = ( - + {i18n.translate('xpack.apm.settingsLinkLabel', { defaultMessage: 'Settings', })} diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx index 81532816d9c83..eb394801f549c 100644 --- a/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx +++ b/x-pack/plugins/canvas/public/components/function_reference_generator/function_reference_generator.tsx @@ -29,7 +29,7 @@ export const FunctionReferenceGenerator: FC = ({ functionRegistry }) => { }; return ( - + Generate function reference ); diff --git a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx index 2877ccf41056d..af1850beb5290 100644 --- a/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx +++ b/x-pack/plugins/canvas/public/components/help_menu/help_menu.tsx @@ -46,13 +46,13 @@ export const HelpMenu: FC = ({ functionRegistry }) => { return ( <> - + {strings.getKeyboardShortcutsLinkLabel()} {FunctionReferenceGenerator ? ( - + ) : null} diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot index cc33ae3526c0c..f2bc9c57cbcc6 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/__stories__/__snapshots__/edit_menu.stories.storyshot @@ -9,7 +9,7 @@ exports[`Storyshots components/WorkpadHeader/EditMenu 2 elements selected 1`] = >