From 0a22f9659483812e02b0bfc1d0d5f38ff16aa4a9 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Wed, 10 Aug 2022 13:24:38 +0200 Subject: [PATCH 01/49] [Security Solution] Incorrect alerts count is displaying under preview results for data view (#138131) * [Security Solution] Incorrect alerts count is displaying under preview results for data view (#137657) * Fix CI Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/rules/rule_preview/helpers.test.ts | 11 +++++++++++ .../components/rules/rule_preview/helpers.ts | 11 ++++++++++- .../components/rules/rule_preview/index.test.tsx | 12 ++++++++++++ .../components/rules/rule_preview/index.tsx | 13 +++++++++++-- .../rules/rule_preview/preview_histogram.test.tsx | 15 ++++++++++++--- .../rules/rule_preview/preview_histogram.tsx | 11 ++++++----- .../rules/rule_preview/use_preview_histogram.tsx | 10 ++++------ .../rules/rule_preview/use_preview_route.tsx | 9 ++++++++- .../components/rules/step_define_rule/index.tsx | 3 +++ .../detection_engine/rules/create/helpers.ts | 3 +++ 10 files changed, 80 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts index 3ef94563edc61..deee988052ef2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DataSourceType } from '../../../pages/detection_engine/rules/types'; import { isNoisy, getTimeframeOptions, @@ -71,6 +72,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: [], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -89,6 +91,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -107,6 +110,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: false, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -125,6 +129,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: [], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -143,6 +148,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: ['test-ml-job-id'], @@ -159,6 +165,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: [], @@ -175,6 +182,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: [], @@ -191,6 +199,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: [], threatMapping: [], machineLearningJobId: [], @@ -207,6 +216,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [ { entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] }, @@ -225,6 +235,7 @@ describe('query_preview/helpers', () => { isThreatQueryBarValid: true, index: ['test-*'], dataViewId: undefined, + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [], machineLearningJobId: [], diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts index a008371e7c127..9ee6628636ad4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/helpers.ts @@ -16,6 +16,8 @@ import type { ChartSeriesConfigs } from '../../../../common/components/charts/co import { getQueryFilter } from '../../../../../common/detection_engine/get_query_filter'; import type { FieldValueQueryBar } from '../query_bar'; import type { ESQuery } from '../../../../../common/typed_json'; +import { DataSourceType } from '../../../pages/detection_engine/rules/types'; + /** * Determines whether or not to display noise warning. * Is considered noisy if alerts/hour rate > 1 @@ -165,6 +167,7 @@ export const getIsRulePreviewDisabled = ({ isThreatQueryBarValid, index, dataViewId, + dataSourceType, threatIndex, threatMapping, machineLearningJobId, @@ -176,14 +179,20 @@ export const getIsRulePreviewDisabled = ({ isThreatQueryBarValid: boolean; index: string[]; dataViewId: string | undefined; + dataSourceType: DataSourceType; threatIndex: string[]; threatMapping: ThreatMapping; machineLearningJobId: string[]; queryBar: FieldValueQueryBar; newTermsFields: string[]; }) => { - if (!isQueryBarValid || ((index == null || index.length === 0) && dataViewId == null)) + if ( + !isQueryBarValid || + (dataSourceType === DataSourceType.DataView && !dataViewId) || + (dataSourceType === DataSourceType.IndexPatterns && index.length === 0) + ) { return true; + } if (ruleType === 'threat_match') { if (!isThreatQueryBarValid || !threatIndex.length || !threatMapping) return true; if ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.test.tsx index bd0cfac44f69d..3b635796edd64 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.test.tsx @@ -9,11 +9,15 @@ import React from 'react'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import type { DataViewBase } from '@kbn/es-query'; +import { fields } from '@kbn/data-plugin/common/mocks'; + import { TestProviders } from '../../../../common/mock'; import type { RulePreviewProps } from '.'; import { RulePreview } from '.'; import { usePreviewRoute } from './use_preview_route'; import { usePreviewHistogram } from './use_preview_histogram'; +import { DataSourceType } from '../../../pages/detection_engine/rules/types'; jest.mock('../../../../common/lib/kibana'); jest.mock('./use_preview_route'); @@ -27,9 +31,17 @@ jest.mock('../../../../common/containers/use_global_time', () => ({ }), })); +const getMockIndexPattern = (): DataViewBase => ({ + fields, + id: '1234', + title: 'logstash-*', +}); + const defaultProps: RulePreviewProps = { ruleType: 'threat_match', index: ['test-*'], + indexPattern: getMockIndexPattern(), + dataSourceType: DataSourceType.IndexPatterns, threatIndex: ['threat-*'], threatMapping: [ { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx index 955e9d5ff3716..3f541344abe5a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx @@ -10,6 +10,7 @@ import dateMath from '@kbn/datemath'; import type { Unit } from '@kbn/datemath'; import type { ThreatMapping, Type } from '@kbn/securitysolution-io-ts-alerting-types'; import styled from 'styled-components'; +import type { DataViewBase } from '@kbn/es-query'; import type { EuiButtonGroupOptionProps, OnTimeChangeProps } from '@elastic/eui'; import { EuiButtonGroup, @@ -39,7 +40,10 @@ import { useStartTransaction } from '../../../../common/lib/apm/use_start_transa import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { Form, UseField, useForm, useFormData } from '../../../../shared_imports'; import { ScheduleItem } from '../schedule_item_form'; -import type { AdvancedPreviewForm } from '../../../pages/detection_engine/rules/types'; +import type { + AdvancedPreviewForm, + DataSourceType, +} from '../../../pages/detection_engine/rules/types'; import { schema } from './schema'; const HelpTextComponent = ( @@ -70,9 +74,11 @@ const advancedOptionsDefaultValue = { export interface RulePreviewProps { index: string[]; + indexPattern: DataViewBase; isDisabled: boolean; query: FieldValueQueryBar; dataViewId?: string; + dataSourceType: DataSourceType; ruleType: Type; threatIndex: string[]; threatMapping: ThreatMapping; @@ -97,7 +103,9 @@ const defaultTimeRange: Unit = 'h'; const RulePreviewComponent: React.FC = ({ index, + indexPattern, dataViewId, + dataSourceType, isDisabled, query, ruleType, @@ -197,6 +205,7 @@ const RulePreviewComponent: React.FC = ({ index, isDisabled, dataViewId, + dataSourceType, query, threatIndex, threatQuery, @@ -334,7 +343,7 @@ const RulePreviewComponent: React.FC = ({ previewId={previewId} addNoiseWarning={addNoiseWarning} spaceId={spaceId} - index={index} + indexPattern={indexPattern} advancedOptions={advancedOptions} /> )} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.test.tsx index 4cbb74f7def21..e9fde44db0b44 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.test.tsx @@ -9,6 +9,9 @@ import React from 'react'; import { render } from '@testing-library/react'; import moment from 'moment'; +import type { DataViewBase } from '@kbn/es-query'; +import { fields } from '@kbn/data-plugin/common/mocks'; + import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { TestProviders } from '../../../../common/mock'; import { usePreviewHistogram } from './use_preview_histogram'; @@ -21,6 +24,12 @@ jest.mock('../../../../common/containers/use_global_time'); jest.mock('./use_preview_histogram'); jest.mock('../../../../common/utils/normalize_time_range'); +const getMockIndexPattern = (): DataViewBase => ({ + fields, + id: '1234', + title: 'logstash-*', +}); + describe('PreviewHistogram', () => { const mockSetQuery = jest.fn(); @@ -58,7 +67,7 @@ describe('PreviewHistogram', () => { previewId={'test-preview-id'} spaceId={'default'} ruleType={'query'} - index={['']} + indexPattern={getMockIndexPattern()} /> ); @@ -89,7 +98,7 @@ describe('PreviewHistogram', () => { previewId={'test-preview-id'} spaceId={'default'} ruleType={'query'} - index={['']} + indexPattern={getMockIndexPattern()} /> ); @@ -141,7 +150,7 @@ describe('PreviewHistogram', () => { previewId={'test-preview-id'} spaceId={'default'} ruleType={'query'} - index={['']} + indexPattern={getMockIndexPattern()} advancedOptions={{ timeframeStart: moment(start, format), timeframeEnd: moment(end, format), diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx index f374c4b192bf2..589e11c17016a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx @@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiLoadingChart } from ' import styled from 'styled-components'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { useDispatch, useSelector } from 'react-redux'; +import type { DataViewBase } from '@kbn/es-query'; import { eventsViewerSelector } from '../../../../common/components/events_viewer/selectors'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useKibana } from '../../../../common/lib/kibana'; @@ -63,7 +64,7 @@ interface PreviewHistogramProps { addNoiseWarning: () => void; spaceId: string; ruleType: Type; - index: string[]; + indexPattern: DataViewBase; advancedOptions?: AdvancedPreviewOptions; } @@ -75,7 +76,7 @@ export const PreviewHistogram = ({ addNoiseWarning, spaceId, ruleType, - index, + indexPattern, advancedOptions, }: PreviewHistogramProps) => { const dispatch = useDispatch(); @@ -99,7 +100,7 @@ export const PreviewHistogram = ({ startDate, endDate, spaceId, - index, + indexPattern, ruleType, }); @@ -118,7 +119,7 @@ export const PreviewHistogram = ({ const { browserFields, - indexPattern, + indexPattern: selectedIndexPattern, runtimeMappings, dataViewId: selectedDataViewId, loading: isLoadingIndexPattern, @@ -225,7 +226,7 @@ export const PreviewHistogram = ({ hasAlertsCrud: false, id: TimelineId.rulePreview, indexNames: [`${DEFAULT_PREVIEW_INDEX}-${spaceId}`], - indexPattern, + indexPattern: selectedIndexPattern, isLive: false, isLoadingIndexPattern, itemsPerPage, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx index facd9a4258e1d..16f57dc402211 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx @@ -7,6 +7,7 @@ import { useMemo } from 'react'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; +import type { DataViewBase } from '@kbn/es-query'; import { useMatrixHistogramCombined } from '../../../../common/containers/matrix_histogram'; import { MatrixHistogramType } from '../../../../../common/search_strategy'; import { convertToBuildEsQuery } from '../../../../common/lib/keury'; @@ -19,8 +20,8 @@ interface PreviewHistogramParams { endDate: string; startDate: string; spaceId: string; - index: string[]; ruleType: Type; + indexPattern: DataViewBase; } export const usePreviewHistogram = ({ @@ -28,17 +29,14 @@ export const usePreviewHistogram = ({ startDate, endDate, spaceId, - index, ruleType, + indexPattern, }: PreviewHistogramParams) => { const { uiSettings } = useKibana().services; const [filterQuery, error] = convertToBuildEsQuery({ config: getEsQueryConfig(uiSettings), - indexPattern: { - fields: [], - title: index == null ? '' : index.join(), - }, + indexPattern, queries: [{ query: `kibana.alert.rule.uuid:${previewId}`, language: 'kuery' }], filters: [], }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx index 94817129fc9d0..d0c543e3c3a31 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx @@ -14,12 +14,16 @@ import { formatPreviewRule } from '../../../pages/detection_engine/rules/create/ import type { FieldValueThreshold } from '../threshold_input'; import type { RulePreviewLogs } from '../../../../../common/detection_engine/schemas/request'; import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; -import type { AdvancedPreviewOptions } from '../../../pages/detection_engine/rules/types'; +import type { + AdvancedPreviewOptions, + DataSourceType, +} from '../../../pages/detection_engine/rules/types'; interface PreviewRouteParams { isDisabled: boolean; index: string[]; dataViewId?: string; + dataSourceType: DataSourceType; threatIndex: string[]; query: FieldValueQueryBar; threatQuery: FieldValueQueryBar; @@ -38,6 +42,7 @@ interface PreviewRouteParams { export const usePreviewRoute = ({ index, dataViewId, + dataSourceType, isDisabled, query, threatIndex, @@ -107,6 +112,7 @@ export const usePreviewRoute = ({ formatPreviewRule({ index, dataViewId, + dataSourceType, query, ruleType, threatIndex, @@ -126,6 +132,7 @@ export const usePreviewRoute = ({ }, [ index, dataViewId, + dataSourceType, isRequestTriggered, query, rule, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 15e0ff4a913b4..d791cafe54b15 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -834,13 +834,16 @@ const StepDefineRuleComponent: FC = ({ ( export const formatPreviewRule = ({ index, dataViewId, + dataSourceType, query, threatIndex, threatQuery, @@ -596,6 +597,7 @@ export const formatPreviewRule = ({ }: { index: string[]; dataViewId?: string; + dataSourceType: DataSourceType; threatIndex: string[]; query: FieldValueQueryBar; threatQuery: FieldValueQueryBar; @@ -614,6 +616,7 @@ export const formatPreviewRule = ({ ...stepDefineDefaultValue, index, dataViewId, + dataSourceType, queryBar: query, ruleType, threatIndex, From 5a24ba3fe3990d654f8c6dc8de0d2e834feb1404 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Wed, 10 Aug 2022 13:48:06 +0200 Subject: [PATCH 02/49] [Security Solution][Detections] Unskip tests for bulk editing index patterns (#138435) **Ticket:** https://github.com/elastic/kibana/issues/138409 **Caused by:** https://github.com/elastic/kibana/pull/138304#issuecomment-1209490032 ## Summary The merge of https://github.com/elastic/kibana/pull/138304 caused unit tests for bulk editing index patterns to start failing. This PR was reverted by https://github.com/elastic/kibana/commit/0bc8cf7f49fe8b9e7bf8c3ebc0f30794472aba84. The tests were skipped in https://github.com/elastic/kibana/commit/26a478355384e010af5bda35c029b2defa9b49e6. These tests are fully functional in `main`. Unskipping them. --- .../rules/bulk_actions/rule_params_modifier.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts index e83fc53c20cf5..c3b4fe0f20134 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts @@ -50,8 +50,7 @@ describe('ruleParamsModifier', () => { expect(editedRuleParams).toHaveProperty('version', ruleParamsMock.version + 1); }); - // FLAKY: https://github.com/elastic/kibana/issues/138409 - describe.skip('index_patterns', () => { + describe('index_patterns', () => { test('should add new index pattern to rule', () => { const editedRuleParams = ruleParamsModifier(ruleParamsMock, [ { From 0c46b0dda966523b65d800c0985e46b99a6c6807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 10 Aug 2022 08:51:07 -0400 Subject: [PATCH 03/49] [APM] Rename JVMs to Metrics (#138437) * [APM] Rename JVMs to Metrics * rename test --- .../components/routing/service_detail/index.tsx | 2 +- .../templates/apm_service_template/index.test.tsx | 12 ++++++------ .../routing/templates/apm_service_template/index.tsx | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx index 4fc2b9836b88c..1835eee137f41 100644 --- a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx @@ -222,7 +222,7 @@ export const serviceDetail = { ...page({ tab: 'nodes', title: i18n.translate('xpack.apm.views.nodes.title', { - defaultMessage: 'JVMs', + defaultMessage: 'Metrics', }), element: , }), diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.test.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.test.tsx index 7ac3b14e7faa9..763d4cf0717a1 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.test.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.test.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { isMetricsTabHidden, isJVMsTabHidden } from '.'; +import { isMetricsTabHidden, isMetricsJVMsTabHidden } from '.'; describe('APM service template', () => { describe('isMetricsTabHidden', () => { @@ -41,8 +41,8 @@ describe('APM service template', () => { }); }); }); - describe('isJVMsTabHidden', () => { - describe('hides JVMs tab', () => { + describe('isMetricsJVMsTabHidden', () => { + describe('hides metrics JVMs tab', () => { [ { agentName: undefined }, { agentName: 'ruby', runtimeName: 'ruby' }, @@ -55,18 +55,18 @@ describe('APM service template', () => { { runtimeName: 'aws_lambda' }, ].map((input) => { it(`when input ${JSON.stringify(input)}`, () => { - expect(isJVMsTabHidden(input)).toBeTruthy(); + expect(isMetricsJVMsTabHidden(input)).toBeTruthy(); }); }); }); - describe('shows JVMs tab', () => { + describe('shows metrics JVMs tab', () => { [ { agentName: 'java' }, { agentName: 'opentelemetry/java' }, { agentName: 'ruby', runtimeName: 'jruby' }, ].map((input) => { it(`when input ${JSON.stringify(input)}`, () => { - expect(isJVMsTabHidden(input)).toBeFalsy(); + expect(isMetricsJVMsTabHidden(input)).toBeFalsy(); }); }); }); diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx index 80957e990f250..7acf14fc0bf01 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx @@ -155,7 +155,7 @@ export function isMetricsTabHidden({ ); } -export function isJVMsTabHidden({ +export function isMetricsJVMsTabHidden({ agentName, runtimeName, }: { @@ -255,9 +255,9 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { query, }), label: i18n.translate('xpack.apm.serviceDetails.nodesTabLabel', { - defaultMessage: 'JVMs', + defaultMessage: 'Metrics', }), - hidden: isJVMsTabHidden({ agentName, runtimeName }), + hidden: isMetricsJVMsTabHidden({ agentName, runtimeName }), }, { key: 'infrastructure', From 27162e08021b116ee567c8d161acd09d17f61041 Mon Sep 17 00:00:00 2001 From: doakalexi <109488926+doakalexi@users.noreply.github.com> Date: Wed, 10 Aug 2022 09:28:39 -0400 Subject: [PATCH 04/49] Fixing test failure (#138156) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../spaces_only/tests/alerting/bulk_edit.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts index 783ec5a9d1628..adbc7650b08b2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts @@ -356,12 +356,13 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { ], }; - const bulkEditResponse = await retry.try(async () => - supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_bulk_edit`) - .set('kbn-xsrf', 'foo') - .send(payload) - .expect(200) + const bulkEditResponse = await retry.try( + async () => + await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_bulk_edit`) + .set('kbn-xsrf', 'foo') + .send(payload) + .expect(200) ); // after applying bulk edit action monitoring still available From 5155d6a5b8d89610ea373a8a2ebec658f91fa969 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 10 Aug 2022 09:29:24 -0400 Subject: [PATCH 05/49] [Security Solution] [Platform] Fixes the alert context menu option to add rule exception (#138291) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/security_solution/common/ecs/index.ts | 4 +++- .../plugins/security_solution/common/ecs/rule/index.ts | 1 + .../timeline_actions/alert_context_menu.tsx | 10 ++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts index e68e25cf4c396..f77ef70aaf96c 100644 --- a/x-pack/plugins/security_solution/common/ecs/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/index.ts @@ -74,5 +74,7 @@ export interface Ecs { Target?: Target; dll?: DllEcs; 'kibana.alert.workflow_status'?: 'open' | 'acknowledged' | 'in-progress' | 'closed'; - 'kibana.alert.rule.parameters'?: { index: string[] }; + // I believe these parameters are all snake cased to correspond with how they are sent "over the wire" as request / response + // Not representative of the parsed types that are camel cased. + 'kibana.alert.rule.parameters'?: { index: string[]; data_view_id?: string }; } diff --git a/x-pack/plugins/security_solution/common/ecs/rule/index.ts b/x-pack/plugins/security_solution/common/ecs/rule/index.ts index ae7e5064a8ece..073bb7db3a3e8 100644 --- a/x-pack/plugins/security_solution/common/ecs/rule/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/rule/index.ts @@ -20,6 +20,7 @@ export interface RuleEcs { from?: string[]; immutable?: boolean[]; index?: string[]; + data_view_id?: string; interval?: string[]; language?: string[]; query?: string[]; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 6982f75d6d6a7..fe2eabec1ea0b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -344,6 +344,15 @@ export const AddExceptionFlyoutWrapper: React.FC return ruleIndices; }, [enrichedAlert, ruleIndices]); + const memoDataViewId = useMemo(() => { + if ( + enrichedAlert != null && + enrichedAlert['kibana.alert.rule.parameters']?.data_view_id != null + ) { + return enrichedAlert['kibana.alert.rule.parameters'].data_view_id; + } + }, [enrichedAlert]); + const isLoading = isLoadingAlertData && isSignalIndexLoading; return ( @@ -351,6 +360,7 @@ export const AddExceptionFlyoutWrapper: React.FC ruleName={ruleName} ruleId={ruleId} ruleIndices={useRuleIndices} + dataViewId={memoDataViewId} exceptionListType={exceptionListType} alertData={enrichedAlert} isAlertDataLoading={isLoading} From 08d14ef2c00576732e3b9a2a9d9da76669c50134 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 10 Aug 2022 07:31:08 -0600 Subject: [PATCH 06/49] [Security Solution] Host/User details flyout, fix ML narrow date range --- .../expandable_host.test.tsx.snap | 341 ------------------ .../host_details/expandable_host.test.tsx | 55 ++- .../host_details/expandable_host.tsx | 26 +- .../user_details/expandable_user.test.tsx | 98 +++++ .../user_details/expandable_user.tsx | 27 +- 5 files changed, 187 insertions(+), 360 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.test.tsx diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap deleted file mode 100644 index 8f14c778cff20..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap +++ /dev/null @@ -1,341 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Expandable Host Component ExpandableHostDetails: rendering it should render the HostOverview of the ExpandableHostDetails 1`] = ` -.c3 { - color: #535966; -} - -.c2 { - word-break: break-word; -} - -.c2 dt { - font-size: 12px !important; -} - -.c2 dd { - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; -} - -.c2 dd > div { - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; -} - -.c1 { - position: relative; -} - -.c1 .euiButtonIcon { - position: absolute; - right: 12px; - top: 6px; - z-index: 2; -} - -.c0 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; -} - -.c0 > * { - max-width: 100%; -} - -.c0 .inspectButtonComponent { - pointer-events: none; - opacity: 0; - -webkit-transition: opacity 250ms ease; - transition: opacity 250ms ease; -} - -.c0:hover .inspectButtonComponent { - pointer-events: auto; - opacity: 1; -} - -.c4 { - padding: 12px; - background: rgba(250,251,253,0.9); - bottom: 0; - left: 0; - position: absolute; - right: 0; - top: 0; - z-index: 1000; -} - -.c5 { - height: 100%; -} - -
-
-
-
-
- Host ID -
-
- - — - -
-
- First seen -
-
- -
-
- Last seen -
-
- -
-
-
-
-
-
- IP addresses -
-
- - — - -
-
- MAC addresses -
-
- - — - -
-
- Platform -
-
- - — - -
-
-
-
-
-
- Operating system -
-
- - — - -
-
- Family -
-
- - — - -
-
- Version -
-
- - — - -
-
- Architecture -
-
- - — - -
-
-
-
-
-
- Cloud provider -
-
- - — - -
-
- Region -
-
- - — - -
-
- Instance ID -
-
- - — - -
-
- Machine type -
-
- - — - -
-
-
- -
-
-`; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx index 945f8bd8b9b52..ecdb41639c5fd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.test.tsx @@ -6,13 +6,43 @@ */ import { mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; import React from 'react'; import '../../../../common/mock/match_media'; import { mockGlobalState, TestProviders } from '../../../../common/mock'; import { ExpandableHostDetails } from './expandable_host'; +import { mockAnomalies } from '../../../../common/components/ml/mock'; +import type { Anomalies } from '../../../../common/components/ml/types'; +import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions'; +const mockDispatch = jest.fn(); +jest.mock('../../../../../common/machine_learning/has_ml_user_permissions'); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); +jest.mock('../../../../common/components/ml/anomaly/anomaly_table_provider', () => ({ + AnomalyTableProvider: ({ + children, + }: { + children: (args: { + anomaliesData: Anomalies; + isLoadingAnomaliesData: boolean; + }) => React.ReactNode; + }) => children({ anomaliesData: mockAnomalies, isLoadingAnomaliesData: false }), +})); describe('Expandable Host Component', () => { + beforeAll(() => { + (hasMlUserPermissions as jest.Mock).mockReturnValue(true); + }); + beforeEach(() => { + jest.clearAllMocks(); + }); const mockProps = { contextID: 'text-context', hostName: 'testHostName', @@ -26,7 +56,7 @@ describe('Expandable Host Component', () => { ); - expect(wrapper.find('ExpandableHostDetails').render()).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="host-overview"]').exists()).toBe(true); }); test('it should render the HostOverview of the ExpandableHostDetails with the correct indices', () => { @@ -40,5 +70,28 @@ describe('Expandable Host Component', () => { mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns ); }); + + test('it should set date range to anomaly date range', async () => { + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="anomaly-score-popover"]').first().simulate('click'); + await waitFor(() => { + wrapper + .find('button[data-test-subj="anomaly-description-narrow-range-link"]') + .first() + .simulate('click'); + }); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'x-pack/security_solution/local/inputs/SET_ABSOLUTE_RANGE_DATE_PICKER', + payload: { + id: 'global', + from: '2019-06-15T06:00:00.000Z', + to: '2019-06-17T06:00:00.000Z', + }, + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index b8009a376bb88..1adeb1304dd2b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiTitle } from '@elastic/eui'; +import { useDispatch } from 'react-redux'; import { HostDetailsLink } from '../../../../common/components/links'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useSourcererDataView } from '../../../../common/containers/sourcerer'; @@ -65,6 +66,7 @@ export const ExpandableHostDetails = ({ (i.e. extraneous endpoint data is retrieved from the backend leading to endpoint data not being returned) */ const { selectedPatterns } = useSourcererDataView(); + const dispatch = useDispatch(); const [loading, { hostDetails: hostOverview }] = useHostDetails({ endDate: to, @@ -72,6 +74,19 @@ export const ExpandableHostDetails = ({ indexNames: selectedPatterns, startDate: from, }); + const narrowDateRange = useCallback( + (score, interval) => { + const fromTo = scoreIntervalToDateTime(score, interval); + dispatch( + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }) + ); + }, + [dispatch] + ); return ( { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} + narrowDateRange={narrowDateRange} hostName={hostName} /> )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.test.tsx new file mode 100644 index 0000000000000..f375209527e5d --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.test.tsx @@ -0,0 +1,98 @@ +/* + * 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 { mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; + +import '../../../../common/mock/match_media'; +import { mockGlobalState, TestProviders } from '../../../../common/mock'; +import { ExpandableUserDetails } from './expandable_user'; +import { mockAnomalies } from '../../../../common/components/ml/mock'; +import type { Anomalies } from '../../../../common/components/ml/types'; +import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions'; +const mockDispatch = jest.fn(); +jest.mock('../../../../../common/machine_learning/has_ml_user_permissions'); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); +jest.mock('../../../../common/components/ml/anomaly/anomaly_table_provider', () => ({ + AnomalyTableProvider: ({ + children, + }: { + children: (args: { + anomaliesData: Anomalies; + isLoadingAnomaliesData: boolean; + }) => React.ReactNode; + }) => children({ anomaliesData: mockAnomalies, isLoadingAnomaliesData: false }), +})); + +describe('Expandable Host Component', () => { + beforeAll(() => { + (hasMlUserPermissions as jest.Mock).mockReturnValue(true); + }); + beforeEach(() => { + jest.clearAllMocks(); + }); + const mockProps = { + contextID: 'text-context', + userName: 'testUserName', + isDraggable: true, + }; + + describe('ExpandableUserDetails: rendering', () => { + test('it should render the UserOverview of the ExpandableUserDetails', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="user-overview"]').exists()).toBe(true); + }); + + test('it should render the UserOverview of the ExpandableUserDetails with the correct indices', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('UserOverview').prop('indexPatterns')).toStrictEqual( + mockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns + ); + }); + + test('it should set date range to anomaly date range', async () => { + const wrapper = mount( + + + + ); + wrapper.find('[data-test-subj="anomaly-score-popover"]').first().simulate('click'); + await waitFor(() => { + wrapper + .find('button[data-test-subj="anomaly-description-narrow-range-link"]') + .first() + .simulate('click'); + }); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'x-pack/security_solution/local/inputs/SET_ABSOLUTE_RANGE_DATE_PICKER', + payload: { + id: 'global', + from: '2019-06-15T06:00:00.000Z', + to: '2019-06-17T06:00:00.000Z', + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx index f1f32802845b0..cbfb7439c83fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx @@ -8,7 +8,8 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; -import React from 'react'; +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; import { UserDetailsLink } from '../../../../common/components/links'; import { UserOverview } from '../../../../overview/components/user_overview'; import { useUserDetails } from '../../../../users/containers/users/details'; @@ -57,6 +58,7 @@ export const ExpandableUserDetails = ({ }: ExpandableUserProps & { contextID: string; isDraggable?: boolean }) => { const { to, from, isInitializing } = useGlobalTime(); const { selectedPatterns } = useSourcererDataView(); + const dispatch = useDispatch(); const [loading, { userDetails }] = useUserDetails({ endDate: to, @@ -66,6 +68,20 @@ export const ExpandableUserDetails = ({ skip: isInitializing, }); + const narrowDateRange = useCallback( + (score, interval) => { + const fromTo = scoreIntervalToDateTime(score, interval); + dispatch( + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }) + ); + }, + [dispatch] + ); + return ( { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} + narrowDateRange={narrowDateRange} indexPatterns={selectedPatterns} /> )} From b8f07a1ccff5301bb3165c12c8730b3f558c4629 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Wed, 10 Aug 2022 09:34:17 -0400 Subject: [PATCH 07/49] [Enterprise Search] New elasticsearchErrorHandler for server routes (#138341) --- .../common/types/error_codes.ts | 1 + .../routes/enterprise_search/config_data.ts | 20 +- .../routes/enterprise_search/connectors.ts | 43 +-- .../enterprise_search/crawler/crawler.ts | 13 +- .../enterprise_search/create_api_key.ts | 31 +- .../routes/enterprise_search/indices.ts | 275 +++++++++--------- .../routes/enterprise_search/mapping.ts | 26 +- .../server/routes/enterprise_search/search.ts | 84 +++--- .../server/utils/create_error.ts | 11 +- .../utils/elasticsearch_error_handler.ts | 73 +++++ .../server/utils/identify_exceptions.ts | 11 +- 11 files changed, 325 insertions(+), 263 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/server/utils/elasticsearch_error_handler.ts diff --git a/x-pack/plugins/enterprise_search/common/types/error_codes.ts b/x-pack/plugins/enterprise_search/common/types/error_codes.ts index a982289fbdb67..327d000003f9b 100644 --- a/x-pack/plugins/enterprise_search/common/types/error_codes.ts +++ b/x-pack/plugins/enterprise_search/common/types/error_codes.ts @@ -12,4 +12,5 @@ export enum ErrorCode { INDEX_NOT_FOUND = 'index_not_found', RESOURCE_NOT_FOUND = 'resource_not_found', UNAUTHORIZED = 'unauthorized', + UNCAUGHT_EXCEPTION = 'uncaught_exception', } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.ts index 5be5bf8cc0373..e65941cb7f20e 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/config_data.ts @@ -5,8 +5,18 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; + import { callEnterpriseSearchConfigAPI } from '../../lib/enterprise_search_config_api'; import { RouteDependencies } from '../../plugin'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; + +const errorMessage = i18n.translate( + 'xpack.enterpriseSearch.server.routes.configData.errorMessage', + { + defaultMessage: 'Error fetching data from Enterprise Search', + } +); export function registerConfigDataRoute({ router, config, log }: RouteDependencies) { router.get( @@ -14,18 +24,18 @@ export function registerConfigDataRoute({ router, config, log }: RouteDependenci path: '/internal/enterprise_search/config_data', validate: false, }, - async (context, request, response) => { - const data = await callEnterpriseSearchConfigAPI({ request, config, log }); + elasticsearchErrorHandler(log, async (context, request, response) => { + const data = await callEnterpriseSearchConfigAPI({ config, log, request }); if ('responseStatus' in data) { return response.customError({ + body: errorMessage, statusCode: data.responseStatus, - body: 'Error fetching data from Enterprise Search', }); } else if (!Object.keys(data).length) { return response.customError({ + body: errorMessage, statusCode: 502, - body: 'Error fetching data from Enterprise Search', }); } else { return response.ok({ @@ -33,6 +43,6 @@ export function registerConfigDataRoute({ router, config, log }: RouteDependenci headers: { 'content-type': 'application/json' }, }); } - } + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 5c2f191eb1395..015567712d648 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -16,8 +16,9 @@ import { updateConnectorScheduling } from '../../lib/connectors/update_connector import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; -export function registerConnectorRoutes({ router }: RouteDependencies) { +export function registerConnectorRoutes({ router, log }: RouteDependencies) { router.post( { path: '/internal/enterprise_search/connectors', @@ -29,25 +30,12 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; try { const body = await addConnector(client, request.body); return response.ok({ body }); } catch (error) { - if (error.statusCode === 403) { - return createError({ - errorCode: ErrorCode.UNAUTHORIZED, - message: i18n.translate( - 'xpack.enterpriseSearch.server.routes.addConnector.unauthorizedError', - { - defaultMessage: 'You do not have the correct access rights to create this resource', - } - ), - response, - statusCode: 403, - }); - } if ( (error as Error).message === ErrorCode.CONNECTOR_DOCUMENT_ALREADY_EXISTS || (error as Error).message === ErrorCode.INDEX_ALREADY_EXISTS @@ -64,15 +52,12 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { statusCode: 409, }); } - return response.customError({ - body: i18n.translate('xpack.enterpriseSearch.server.routes.addConnector.error', { - defaultMessage: 'Error fetching data from Enterprise Search', - }), - statusCode: 502, - }); + + throw error; } - } + }) ); + router.post( { path: '/internal/enterprise_search/connectors/{connectorId}/configuration', @@ -86,7 +71,7 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; try { await updateConnectorConfiguration(client, request.params.connectorId, request.body); @@ -99,8 +84,9 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { statusCode: 502, }); } - } + }) ); + router.post( { path: '/internal/enterprise_search/connectors/{connectorId}/scheduling', @@ -111,7 +97,7 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; try { await updateConnectorScheduling(client, request.params.connectorId, request.body); @@ -124,8 +110,9 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { statusCode: 502, }); } - } + }) ); + router.post( { path: '/internal/enterprise_search/connectors/{connectorId}/start_sync', @@ -135,7 +122,7 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; try { await startConnectorSync(client, request.params.connectorId); @@ -148,6 +135,6 @@ export function registerConnectorRoutes({ router }: RouteDependencies) { statusCode: 502, }); } - } + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts index eec442fcaef7a..41a00607cfc25 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts @@ -6,7 +6,6 @@ */ import { schema } from '@kbn/config-schema'; - import { i18n } from '@kbn/i18n'; import { ErrorCode } from '../../../../common/types/error_codes'; @@ -15,13 +14,14 @@ import { fetchCrawlerByIndexName } from '../../../lib/crawler/fetch_crawlers'; import { RouteDependencies } from '../../../plugin'; import { createError } from '../../../utils/create_error'; +import { elasticsearchErrorHandler } from '../../../utils/elasticsearch_error_handler'; import { registerCrawlerCrawlRulesRoutes } from './crawler_crawl_rules'; import { registerCrawlerEntryPointRoutes } from './crawler_entry_points'; import { registerCrawlerSitemapRoutes } from './crawler_sitemaps'; export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { - const { router, enterpriseSearchRequestHandler } = routeDependencies; + const { router, enterpriseSearchRequestHandler, log } = routeDependencies; router.post( { @@ -33,11 +33,13 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; + const indexExists = await client.asCurrentUser.indices.exists({ index: request.body.index_name, }); + if (indexExists) { return createError({ errorCode: ErrorCode.INDEX_ALREADY_EXISTS, @@ -51,7 +53,9 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { statusCode: 409, }); } + const crawler = await fetchCrawlerByIndexName(client, request.body.index_name); + if (crawler) { return createError({ errorCode: ErrorCode.CRAWLER_ALREADY_EXISTS, @@ -81,10 +85,11 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { statusCode: 409, }); } + return enterpriseSearchRequestHandler.createRequest({ path: '/api/ent/v1/internal/indices', })(context, request, response); - } + }) ); router.post( diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/create_api_key.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/create_api_key.ts index 2a0f745f2e4c8..da3b8c736146e 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/create_api_key.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/create_api_key.ts @@ -11,9 +11,10 @@ import { SecurityPluginStart } from '@kbn/security-plugin/server'; import { createApiKey } from '../../lib/indices/create_api_key'; import { RouteDependencies } from '../../plugin'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; export function registerCreateAPIKeyRoute( - { router }: RouteDependencies, + { log, router }: RouteDependencies, security: SecurityPluginStart ) { router.post( @@ -28,24 +29,20 @@ export function registerCreateAPIKeyRoute( }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { indexName } = request.params; const { keyName } = request.body; - try { - const createResponse = await createApiKey(request, security, indexName, keyName); - if (!createResponse) { - throw new Error('Unable to create API Key'); - } - return response.ok({ - body: { apiKey: createResponse }, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error creating API Key', - statusCode: 502, - }); + + const createResponse = await createApiKey(request, security, indexName, keyName); + + if (!createResponse) { + throw new Error('Unable to create API Key'); } - } + + return response.ok({ + body: { apiKey: createResponse }, + headers: { 'content-type': 'application/json' }, + }); + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index c254d0300e94d..bf44bab16fc2a 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -19,27 +19,23 @@ import { fetchIndices } from '../../lib/indices/fetch_indices'; import { generateApiKey } from '../../lib/indices/generate_api_key'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; import { isIndexNotFoundException } from '../../utils/identify_exceptions'; -export function registerIndexRoutes({ router }: RouteDependencies) { +export function registerIndexRoutes({ router, log }: RouteDependencies) { router.get( { path: '/internal/enterprise_search/search_indices', validate: false }, - async (context, _, response) => { + elasticsearchErrorHandler(log, async (context, _, response) => { const { client } = (await context.core).elasticsearch; - try { - const indices = await fetchIndices(client, '*', false, true); - return response.ok({ - body: indices, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, - }); - } - } + const indices = await fetchIndices(client, '*', false, true); + + return response.ok({ + body: indices, + headers: { 'content-type': 'application/json' }, + }); + }) ); + router.get( { path: '/internal/enterprise_search/indices', @@ -52,7 +48,7 @@ export function registerIndexRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { page, size, @@ -60,44 +56,40 @@ export function registerIndexRoutes({ router }: RouteDependencies) { search_query: searchQuery, } = request.query; const { client } = (await context.core).elasticsearch; - try { - const indexPattern = searchQuery ? `*${searchQuery}*` : '*'; - const totalIndices = await fetchIndices(client, indexPattern, !!returnHiddenIndices, false); - const totalResults = totalIndices.length; - const totalPages = Math.ceil(totalResults / size) || 1; - const startIndex = (page - 1) * size; - const endIndex = page * size; - const selectedIndices = totalIndices.slice(startIndex, endIndex); - const indexNames = selectedIndices.map(({ name }) => name); - const connectors = await fetchConnectors(client, indexNames); - const crawlers = await fetchCrawlers(client, indexNames); - const indices = selectedIndices.map((index) => ({ - ...index, - connector: connectors.find((connector) => connector.index_name === index.name), - crawler: crawlers.find((crawler) => crawler.index_name === index.name), - })); - return response.ok({ - body: { - indices, - meta: { - page: { - current: page, - size: indices.length, - total_pages: totalPages, - total_results: totalResults, - }, + + const indexPattern = searchQuery ? `*${searchQuery}*` : '*'; + const totalIndices = await fetchIndices(client, indexPattern, !!returnHiddenIndices, false); + const totalResults = totalIndices.length; + const totalPages = Math.ceil(totalResults / size) || 1; + const startIndex = (page - 1) * size; + const endIndex = page * size; + const selectedIndices = totalIndices.slice(startIndex, endIndex); + const indexNames = selectedIndices.map(({ name }) => name); + const connectors = await fetchConnectors(client, indexNames); + const crawlers = await fetchCrawlers(client, indexNames); + const indices = selectedIndices.map((index) => ({ + ...index, + connector: connectors.find((connector) => connector.index_name === index.name), + crawler: crawlers.find((crawler) => crawler.index_name === index.name), + })); + + return response.ok({ + body: { + indices, + meta: { + page: { + current: page, + size: indices.length, + total_pages: totalPages, + total_results: totalResults, }, }, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error fetching index data from Elasticsearch', - statusCode: 502, - }); - } - } + }, + headers: { 'content-type': 'application/json' }, + }); + }) ); + router.get( { path: '/internal/enterprise_search/indices/{indexName}', @@ -107,9 +99,10 @@ export function registerIndexRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { indexName } = request.params; const { client } = (await context.core).elasticsearch; + try { const index = await fetchIndex(client, indexName); return response.ok({ @@ -125,13 +118,12 @@ export function registerIndexRoutes({ router }: RouteDependencies) { statusCode: 404, }); } - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, - }); + + throw error; } - } + }) ); + router.get( { path: '/internal/enterprise_search/indices/{indexName}/exists', @@ -141,25 +133,35 @@ export function registerIndexRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { indexName } = request.params; const { client } = (await context.core).elasticsearch; + let indexExists: boolean; + try { - const indexExists = await client.asCurrentUser.indices.exists({ index: indexName }); - return response.ok({ - body: { - exists: indexExists, - }, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, - }); + indexExists = await client.asCurrentUser.indices.exists({ index: indexName }); + } catch (e) { + log.warn( + i18n.translate('xpack.enterpriseSearch.server.routes.indices.existsErrorLogMessage', { + defaultMessage: 'An error occured while resolving request to {requestUrl}', + values: { + requestUrl: request.url.toString(), + }, + }) + ); + log.warn(e); + indexExists = false; } - } + + return response.ok({ + body: { + exists: indexExists, + }, + headers: { 'content-type': 'application/json' }, + }); + }) ); + router.post( { path: '/internal/enterprise_search/indices/{indexName}/api_key', @@ -169,23 +171,19 @@ export function registerIndexRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { indexName } = request.params; const { client } = (await context.core).elasticsearch; - try { - const apiKey = await generateApiKey(client, indexName); - return response.ok({ - body: apiKey, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, - }); - } - } + + const apiKey = await generateApiKey(client, indexName); + + return response.ok({ + body: apiKey, + headers: { 'content-type': 'application/json' }, + }); + }) ); + router.post( { path: '/internal/enterprise_search/indices', @@ -196,67 +194,66 @@ export function registerIndexRoutes({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { ['index_name']: indexName, language } = request.body; const { client } = (await context.core).elasticsearch; - try { - const indexExists = await client.asCurrentUser.indices.exists({ - index: request.body.index_name, + + const indexExists = await client.asCurrentUser.indices.exists({ + index: request.body.index_name, + }); + + if (indexExists) { + return createError({ + errorCode: ErrorCode.INDEX_ALREADY_EXISTS, + message: i18n.translate( + 'xpack.enterpriseSearch.server.routes.createApiIndex.indexExistsError', + { + defaultMessage: 'This index already exists', + } + ), + response, + statusCode: 409, }); - if (indexExists) { - return createError({ - errorCode: ErrorCode.INDEX_ALREADY_EXISTS, - message: i18n.translate( - 'xpack.enterpriseSearch.server.routes.createApiIndex.indexExistsError', - { - defaultMessage: 'This index already exists', - } - ), - response, - statusCode: 409, - }); - } - const crawler = await fetchCrawlerByIndexName(client, request.body.index_name); - if (crawler) { - return createError({ - errorCode: ErrorCode.CRAWLER_ALREADY_EXISTS, - message: i18n.translate( - 'xpack.enterpriseSearch.server.routes.createApiIndex.crawlerExistsError', - { - defaultMessage: 'A crawler for this index already exists', - } - ), - response, - statusCode: 409, - }); - } + } - const connector = await fetchConnectorByIndexName(client, request.body.index_name); + const crawler = await fetchCrawlerByIndexName(client, request.body.index_name); - if (connector) { - return createError({ - errorCode: ErrorCode.CONNECTOR_DOCUMENT_ALREADY_EXISTS, - message: i18n.translate( - 'xpack.enterpriseSearch.server.routes.createApiIndex.connectorExistsError', - { - defaultMessage: 'A connector for this index already exists', - } - ), - response, - statusCode: 409, - }); - } - const createIndexResponse = await createApiIndex(client, indexName, language); - return response.ok({ - body: createIndexResponse, - headers: { 'content-type': 'application/json' }, + if (crawler) { + return createError({ + errorCode: ErrorCode.CRAWLER_ALREADY_EXISTS, + message: i18n.translate( + 'xpack.enterpriseSearch.server.routes.createApiIndex.crawlerExistsError', + { + defaultMessage: 'A crawler for this index already exists', + } + ), + response, + statusCode: 409, }); - } catch (error) { - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, + } + + const connector = await fetchConnectorByIndexName(client, request.body.index_name); + + if (connector) { + return createError({ + errorCode: ErrorCode.CONNECTOR_DOCUMENT_ALREADY_EXISTS, + message: i18n.translate( + 'xpack.enterpriseSearch.server.routes.createApiIndex.connectorExistsError', + { + defaultMessage: 'A connector for this index already exists', + } + ), + response, + statusCode: 409, }); } - } + + const createIndexResponse = await createApiIndex(client, indexName, language); + + return response.ok({ + body: createIndexResponse, + headers: { 'content-type': 'application/json' }, + }); + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts index 506fcaf73bd1f..b963157694acf 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/mapping.ts @@ -9,8 +9,9 @@ import { schema } from '@kbn/config-schema'; import { fetchMapping } from '../../lib/fetch_mapping'; import { RouteDependencies } from '../../plugin'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; -export function registerMappingRoute({ router }: RouteDependencies) { +export function registerMappingRoute({ router, log }: RouteDependencies) { router.get( { path: '/internal/enterprise_search/mappings/{index_name}', @@ -20,20 +21,15 @@ export function registerMappingRoute({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - try { - const mapping = await fetchMapping(client, request.params.index_name); - return response.ok({ - body: mapping, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, - }); - } - } + + const mapping = await fetchMapping(client, request.params.index_name); + + return response.ok({ + body: mapping, + headers: { 'content-type': 'application/json' }, + }); + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts index 68391bf8b4f4b..ee57f4a59c568 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/search.ts @@ -13,6 +13,7 @@ import { ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT } from '../../../common/c import { fetchSearchResults } from '../../lib/fetch_search_results'; import { RouteDependencies } from '../../plugin'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; const calculateMeta = (searchResults: SearchResponseBody, page: number, size: number) => { let totalResults = 0; @@ -35,7 +36,7 @@ const calculateMeta = (searchResults: SearchResponseBody, page: number, size: nu }; }; -export function registerSearchRoute({ router }: RouteDependencies) { +export function registerSearchRoute({ router, log }: RouteDependencies) { router.get( { path: '/internal/enterprise_search/indices/{index_name}/search', @@ -52,34 +53,29 @@ export function registerSearchRoute({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; const { page = 0, size = ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT } = request.query; const from = page * size; - try { - const searchResults: SearchResponseBody = await fetchSearchResults( - client, - request.params.index_name, - '', - from, - size - ); - return response.ok({ - body: { - meta: calculateMeta(searchResults, page, size), - results: searchResults, - }, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, - }); - } - } + const searchResults: SearchResponseBody = await fetchSearchResults( + client, + request.params.index_name, + '', + from, + size + ); + + return response.ok({ + body: { + meta: calculateMeta(searchResults, page, size), + results: searchResults, + }, + headers: { 'content-type': 'application/json' }, + }); + }) ); + router.get( { path: '/internal/enterprise_search/indices/{index_name}/search/{query}', @@ -97,32 +93,26 @@ export function registerSearchRoute({ router }: RouteDependencies) { }), }, }, - async (context, request, response) => { + elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; const { page = 0, size = ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT } = request.query; const from = page * size; - try { - const searchResults = await fetchSearchResults( - client, - request.params.index_name, - request.params.query, - from, - size - ); - return response.ok({ - body: { - meta: calculateMeta(searchResults, page, size), - results: searchResults, - }, - headers: { 'content-type': 'application/json' }, - }); - } catch (error) { - return response.customError({ - body: 'Error fetching data from Enterprise Search', - statusCode: 502, - }); - } - } + const searchResults = await fetchSearchResults( + client, + request.params.index_name, + request.params.query, + from, + size + ); + + return response.ok({ + body: { + meta: calculateMeta(searchResults, page, size), + results: searchResults, + }, + headers: { 'content-type': 'application/json' }, + }); + }) ); } diff --git a/x-pack/plugins/enterprise_search/server/utils/create_error.ts b/x-pack/plugins/enterprise_search/server/utils/create_error.ts index 388c540148f6c..ca9fac814f4c9 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_error.ts +++ b/x-pack/plugins/enterprise_search/server/utils/create_error.ts @@ -9,16 +9,19 @@ import { KibanaResponseFactory } from '@kbn/core-http-server'; import { ErrorCode } from '../../common/types/error_codes'; +export interface EnterpriseSearchError { + errorCode: ErrorCode; + message: string; + statusCode: number; +} + export function createError({ errorCode, message, response, statusCode, -}: { - errorCode: ErrorCode; - message: string; +}: EnterpriseSearchError & { response: KibanaResponseFactory; - statusCode: number; }) { return response.customError({ body: { diff --git a/x-pack/plugins/enterprise_search/server/utils/elasticsearch_error_handler.ts b/x-pack/plugins/enterprise_search/server/utils/elasticsearch_error_handler.ts new file mode 100644 index 0000000000000..3f938149e5a16 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/elasticsearch_error_handler.ts @@ -0,0 +1,73 @@ +/* + * 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 { RequestHandler } from '@kbn/core/server'; +import { Logger } from '@kbn/core/server'; + +import { i18n } from '@kbn/i18n'; + +import { ErrorCode } from '../../common/types/error_codes'; + +import { createError, EnterpriseSearchError } from './create_error'; +import { isUnauthorizedException } from './identify_exceptions'; + +export function elasticsearchErrorHandler( + log: Logger, + requestHandler: RequestHandler +): RequestHandler { + return async (context, request, response) => { + try { + return await requestHandler(context, request, response); + } catch (error) { + let enterpriseSearchError: EnterpriseSearchError | undefined; + + if (isUnauthorizedException(error)) { + enterpriseSearchError = { + errorCode: ErrorCode.UNAUTHORIZED, + message: i18n.translate('xpack.enterpriseSearch.server.routes.unauthorizedError', { + defaultMessage: 'You do not have sufficient permissions.', + }), + statusCode: 403, + }; + } else { + enterpriseSearchError = { + errorCode: ErrorCode.UNCAUGHT_EXCEPTION, + message: i18n.translate('xpack.enterpriseSearch.server.routes.uncaughtExceptionError', { + defaultMessage: 'Enterprise Search encountered an error.', + }), + statusCode: 502, + }; + } + + if (enterpriseSearchError !== undefined) { + log.error( + i18n.translate('xpack.enterpriseSearch.server.routes.errorLogMessage', { + defaultMessage: + 'An error occured while resolving request to {requestUrl}: {errorMessage}', + values: { + errorMessage: enterpriseSearchError.message, + requestUrl: request.url.toString(), + }, + }) + ); + log.error(error); + return createError({ + ...enterpriseSearchError, + message: i18n.translate('xpack.enterpriseSearch.server.routes.checkKibanaLogsMessage', { + defaultMessage: '{errorMessage} Check Kibana Server logs for details.', + values: { + errorMessage: enterpriseSearchError.message, + }, + }), + response, + }); + } + + throw error; + } + }; +} diff --git a/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts b/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts index ce0349b95c207..7bc13135343dc 100644 --- a/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts +++ b/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts @@ -5,20 +5,23 @@ * 2.0. */ -interface ErrorResponse { +interface ElasticsearchResponseError { meta?: { body?: { error?: { type: string; }; }; + statusCode?: number; }; name: 'ResponseError'; - statusCode: string; } -export const isIndexNotFoundException = (error: ErrorResponse) => +export const isIndexNotFoundException = (error: ElasticsearchResponseError) => error?.meta?.body?.error?.type === 'index_not_found_exception'; -export const isResourceAlreadyExistsException = (error: ErrorResponse) => +export const isResourceAlreadyExistsException = (error: ElasticsearchResponseError) => error?.meta?.body?.error?.type === 'resource_already_exists_exception'; + +export const isUnauthorizedException = (error: ElasticsearchResponseError) => + error.meta?.statusCode === 403; From e2401c97e406ca669ba12d326bb340f8ca8a5db1 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 10 Aug 2022 10:02:17 -0400 Subject: [PATCH 08/49] [ci] Fix build argument labels (#138322) * [ci] Fix build arguments * without quotes --- .buildkite/scripts/build_kibana.sh | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.buildkite/scripts/build_kibana.sh b/.buildkite/scripts/build_kibana.sh index 54e5273eb4c68..90f9da8ac8de4 100755 --- a/.buildkite/scripts/build_kibana.sh +++ b/.buildkite/scripts/build_kibana.sh @@ -9,17 +9,12 @@ export KBN_NP_PLUGINS_BUILT=true echo "--- Build Kibana Distribution" BUILD_ARGS="" -if is_pr_with_label "ci:build-all-platforms"; then - BUILD_ARGS="--all-platforms --skip-os-packages" -fi -if is_pr_with_label "ci:build-os-packages"; then - BUILD_ARGS="--all-platforms --docker-cross-compile" -fi -if ! is_pr_with_label "ci:build-canvas-shareable-runtime"; then - BUILD_ARGS="$BUILD_ARGS --skip-canvas-shareable-runtime" -fi - -node scripts/build "$BUILD_ARGS" +is_pr_with_label "ci:build-all-platforms" && BUILD_ARGS="--all-platforms" +is_pr_with_label "ci:build-docker-cross-compile" && BUILD_ARG="$BUILD_ARGS --docker-cross-compile" +is_pr_with_label "ci:build-os-packages" || BUILD_ARGS="$BUILD_ARGS --skip-os-packages" +is_pr_with_label "ci:build-canvas-shareable-runtime" || BUILD_ARGS="$BUILD_ARGS --skip-canvas-shareable-runtime" +is_pr_with_label "ci:build-docker-contexts" || BUILD_ARGS="$BUILD_ARGS --skip-docker-contexts" +node scripts/build $BUILD_ARGS if is_pr_with_label "ci:build-cloud-image"; then echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co From 05da4a3965dbbfa88f837f54dc61b9caf337e592 Mon Sep 17 00:00:00 2001 From: doakalexi <109488926+doakalexi@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:06:39 -0400 Subject: [PATCH 09/49] [ResponseOps][Alerting] Index threshold alert can't use unsigned long data type (#138452) * Adding unsigned_long * Reverting comment change Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/data/routes/fields.ts | 1 + .../common/lib/es_test_index_tool.ts | 3 ++ .../index_threshold/alert.ts | 39 +++++++++++++++++++ .../index_threshold/create_test_data.ts | 1 + 4 files changed, 44 insertions(+) diff --git a/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts index e2e9b5967305d..e2c4302afe352 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts @@ -138,4 +138,5 @@ const normalizedFieldTypes: Record = { float: 'number', half_float: 'number', scaled_float: 'number', + unsigned_long: 'number', }; diff --git a/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts b/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts index 524709e6c02a7..f66ad0bcd46e1 100644 --- a/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts +++ b/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts @@ -50,6 +50,9 @@ export class ESTestIndexTool { testedValue: { type: 'long', }, + testedValueUnsigned: { + type: 'unsigned_long', + }, group: { type: 'keyword', }, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index d24db787c5fdd..afa9032ea3419 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -265,6 +265,45 @@ export default function ruleTests({ getService }: FtrProviderContext) { expect(inGroup2).to.be.greaterThan(0); }); + it('runs correctly: max grouped on unsigned long', async () => { + await createRule({ + name: 'never fire', + aggType: 'max', + aggField: 'testedValueUnsigned', + groupBy: 'top', + termField: 'group', + termSize: 2, + thresholdComparator: '<', + threshold: [Number.MAX_SAFE_INTEGER], + }); + + await createRule({ + name: 'always fire', + aggType: 'max', + aggField: 'testedValueUnsigned', + groupBy: 'top', + termField: 'group', + termSize: 2, // two actions will fire each interval + thresholdComparator: '>=', + threshold: [Number.MAX_SAFE_INTEGER], + }); + + // create some more documents in the first group + await createEsDocumentsInGroups(1); + + const docs = await waitForDocs(4); + + for (const doc of docs) { + const { name, message } = doc._source.params; + + expect(name).to.be('always fire'); + + const messagePattern = + /alert 'always fire' is active for group \'group-\d\':\n\n- Value: \d+\n- Conditions Met: max\(testedValueUnsigned\) is greater than or equal to \d+ over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + } + }); + it('runs correctly: min grouped', async () => { await createRule({ name: 'never fire', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/create_test_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/create_test_data.ts index 80438a856e8ac..32f1322d12d4d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/create_test_data.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/create_test_data.ts @@ -65,6 +65,7 @@ async function createEsDocument( date: new Date(epochMillis).toISOString(), date_epoch_millis: epochMillis, testedValue, + testedValueUnsigned: '18446744073709551615', group, '@timestamp': new Date(epochMillis).toISOString(), }; From 9452be575e807fe0f97fe3ed7141e8795450c8db Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Wed, 10 Aug 2022 16:19:43 +0200 Subject: [PATCH 10/49] [Enterprise Search] Fix React rendering issues (#138496) --- .../components/api_key/api_key.tsx | 35 +- .../method_connector/method_connector.tsx | 86 +++-- .../document_list/document_list.tsx | 127 ++++--- .../generate_api_key_modal/modal.test.tsx | 8 +- .../generate_api_key_modal/modal.tsx | 27 +- .../connector/api_key_configuration.tsx | 87 ++--- .../connector/connector_configuration.tsx | 333 +++++++++--------- .../connector_configuration_config.tsx | 48 +-- .../connector/connector_overview_panels.tsx | 26 +- .../search_indices/search_indices.tsx | 94 ++--- 10 files changed, 434 insertions(+), 437 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/api_key/api_key.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/api_key/api_key.tsx index 1fc67699c9a6d..f24fc6234d0c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/api_key/api_key.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/api_key/api_key.tsx @@ -7,36 +7,23 @@ import React from 'react'; -import { EuiCodeBlock, EuiFormLabel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiCodeBlock, EuiFormLabel, EuiSpacer } from '@elastic/eui'; interface ApiKeyProps { - actions?: React.ReactNode; apiKey: string; label?: string; } -export const ApiKey: React.FC = ({ apiKey, label, actions }) => { - const codeBlock = ( +export const ApiKey: React.FC = ({ apiKey, label }) => ( + <> + {label && ( + <> + {label} + + + )} {apiKey} - ); - return ( - <> - {label && ( - <> - {label} - - - )} - {actions ? ( - - {codeBlock} - {actions} - - ) : ( - codeBlock - )} - - ); -}; + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx index a3d3456e21f40..59d43d8635e1b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx @@ -69,49 +69,6 @@ export const MethodConnector: React.FC = () => { const { setIsModalVisible } = useActions(AddConnectorPackageLogic); const { fullIndexName, language } = useValues(NewSearchIndexLogic); - const confirmModal = isModalVisible && ( - { - event?.preventDefault(); - setIsModalVisible(false); - }} - onConfirm={(event) => { - event.preventDefault(); - makeRequest({ deleteExistingConnector: true, indexName: fullIndexName, language }); - }} - cancelButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.confirmModal.cancelButton.label', - { - defaultMessage: 'Cancel', - } - )} - confirmButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.confirmModal.confirmButton.label', - { - defaultMessage: 'Replace configuration', - } - )} - defaultFocusedButton="confirm" - > - {i18n.translate( - 'xpack.enterpriseSearch.content..newIndex.steps.buildConnector.confirmModal.description', - { - defaultMessage: - 'A deleted index named {indexName} was originally tied to an existing connector configuration. Would you like to replace the existing connector configuration with a new one?', - values: { - indexName: fullIndexName, - }, - } - )} - - ); - return ( { BUILD_SEARCH_EXPERIENCE_STEP, ]} /> - {confirmModal} + {isModalVisible && ( + { + event?.preventDefault(); + setIsModalVisible(false); + }} + onConfirm={(event) => { + event.preventDefault(); + makeRequest({ deleteExistingConnector: true, indexName: fullIndexName, language }); + }} + cancelButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.confirmModal.cancelButton.label', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.confirmModal.confirmButton.label', + { + defaultMessage: 'Replace configuration', + } + )} + defaultFocusedButton="confirm" + > + {i18n.translate( + 'xpack.enterpriseSearch.content..newIndex.steps.buildConnector.confirmModal.description', + { + defaultMessage: + 'A deleted index named {indexName} was originally tied to an existing connector configuration. Would you like to replace the existing connector configuration with a new one?', + values: { + indexName: fullIndexName, + }, + } + )} + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/document_list/document_list.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/document_list/document_list.tsx index 779c636968435..43ed12f040d0c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/document_list/document_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/document_list/document_list.tsx @@ -57,72 +57,10 @@ export const DocumentList: React.FC = () => { return []; }; - const docsPerPageButton = ( - { - setIsPopoverOpen(true); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.pagination.itemsPerPage', - { - defaultMessage: 'Documents per page: {docPerPage}', - values: { docPerPage: docsPerPage }, - } - )} - - ); - const getIconType = (size: number) => { return size === docsPerPage ? 'check' : 'empty'; }; - const docsPerPageOptions = [ - { - setIsPopoverOpen(false); - setDocsPerPage(10); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', - { defaultMessage: '{docCount} documents', values: { docCount: 10 } } - )} - , - - { - setIsPopoverOpen(false); - setDocsPerPage(25); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', - { defaultMessage: '{docCount} documents', values: { docCount: 25 } } - )} - , - { - setIsPopoverOpen(false); - setDocsPerPage(50); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', - { defaultMessage: '{docCount} documents', values: { docCount: 50 } } - )} - , - ]; - return ( <> { 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.docsPerPage', { defaultMessage: 'Document count per page dropdown' } )} - button={docsPerPageButton} + button={ + { + setIsPopoverOpen(true); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.pagination.itemsPerPage', + { + defaultMessage: 'Documents per page: {docPerPage}', + values: { docPerPage: docsPerPage }, + } + )} + + } isOpen={isPopoverOpen} closePopover={() => { setIsPopoverOpen(false); @@ -183,7 +138,51 @@ export const DocumentList: React.FC = () => { panelPaddingSize="none" anchorPosition="downLeft" > - + { + setIsPopoverOpen(false); + setDocsPerPage(10); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', + { defaultMessage: '{docCount} documents', values: { docCount: 10 } } + )} + , + + { + setIsPopoverOpen(false); + setDocsPerPage(25); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', + { defaultMessage: '{docCount} documents', values: { docCount: 25 } } + )} + , + { + setIsPopoverOpen(false); + setDocsPerPage(50); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', + { defaultMessage: '{docCount} documents', values: { docCount: 50 } } + )} + , + ]} + /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.test.tsx index 325d52ab2d2fd..b579df9da5c8b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.test.tsx @@ -11,14 +11,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiModal, EuiFieldText } from '@elastic/eui'; +import { EuiModal, EuiFieldText, EuiCodeBlock } from '@elastic/eui'; const mockActions = { makeRequest: jest.fn(), setKeyName: jest.fn() }; const mockValues = { apiKey: '', isLoading: false, isSuccess: false, keyName: '' }; -import { ApiKey } from '../../../api_key/api_key'; - import { GenerateApiKeyModal } from './modal'; const onCloseMock = jest.fn(); @@ -84,8 +82,8 @@ describe('GenerateApiKeyModal', () => { ); expect(wrapper.find(EuiFieldText)).toHaveLength(0); expect(wrapper.find('[data-test-subj="generateApiKeyButton"]')).toHaveLength(0); - expect(wrapper.find(ApiKey)).toHaveLength(1); - expect(wrapper.find(ApiKey).prop('apiKey')).toEqual('apiKeyFromBackend123123=='); + expect(wrapper.find(EuiCodeBlock)).toHaveLength(1); + expect(wrapper.find(EuiCodeBlock).children().text()).toEqual('apiKeyFromBackend123123=='); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx index fcb95fd4ade32..2690deea0c6de 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx @@ -26,14 +26,14 @@ import { EuiText, EuiSpacer, EuiLink, + EuiFormLabel, + EuiCodeBlock, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { docLinks } from '../../../../../shared/doc_links'; -import { ApiKey } from '../../../api_key/api_key'; - import { GenerateApiKeyModalLogic } from './generate_api_key_modal.logic'; interface GenerateApiKeyModalProps { @@ -114,10 +114,21 @@ export const GenerateApiKeyModal: React.FC = ({ indexN ) : ( - {keyName} + + + + + {apiKey} + + + = ({ indexN href={encodeURI(`data:text/csv;charset=utf-8,${apiKey}`)} download={`${keyName}.csv`} /> - } - /> + + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx index c435bac2a5811..19974d4b281b3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/api_key_configuration.tsx @@ -23,6 +23,43 @@ import { Status } from '../../../../../../common/types/api'; import { GenerateConnectorApiKeyApiLogic } from '../../../api/connector_package/generate_connector_api_key_api_logic'; import { ApiKey } from '../../api_key/api_key'; +const ConfirmModal: React.FC<{ + onCancel: () => void; + onConfirm: () => void; +}> = ({ onCancel, onConfirm }) => ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description', + { + defaultMessage: + 'Generating a new API key will invalidate the previous key. Are you sure you want to generate a new API key? This can not be undone.', + } + )} + +); + export const ApiKeyConfig: React.FC<{ hasApiKey: boolean; indexName: string }> = ({ hasApiKey, indexName, @@ -44,50 +81,18 @@ export const ApiKeyConfig: React.FC<{ hasApiKey: boolean; indexName: string }> = const [isModalVisible, setIsModalVisible] = useState(false); - const confirmModal = ( - { - event?.preventDefault(); - setIsModalVisible(false); - }} - onConfirm={(event) => { - event.preventDefault(); - makeRequest({ indexName }); - setIsModalVisible(false); - }} - cancelButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.cancelButton.label', - { - defaultMessage: 'Cancel', - } - )} - confirmButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.confirmButton.label', - { - defaultMessage: 'Generate API key', - } - )} - defaultFocusedButton="confirm" - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description', - { - defaultMessage: - 'Generating a new API key will invalidate the previous key. Are you sure you want to generate a new API key? This can not be undone.', - } - )} - - ); + const onCancel = () => { + setIsModalVisible(false); + }; + + const onConfirm = () => { + makeRequest({ indexName }); + setIsModalVisible(false); + }; return ( - {isModalVisible && confirmModal} + {isModalVisible && } {i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx index 596b1837323da..acd8c856c3893 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx @@ -55,172 +55,6 @@ export const ConnectorConfiguration: React.FC = () => { const hasApiKey = !!(indexData.connector.api_key_id ?? apiKeyData); - const ScheduleStep: React.FC = () => ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.scheduleSync.description', - { - defaultMessage: - 'Once your connectors are configured to your liking, don’t forget to set a recurring sync schedule to make sure your documents are indexed and relevant. You can also trigger a one-time sync without enabling a sync schedule.', - } - )} - - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.button.label', - { - defaultMessage: 'Set schedule and sync', - } - )} - - - - - - ); - - const ConnectorPackage: React.FC = () => ( - <> - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.firstParagraph', - { - defaultMessage: - 'The connectors repository contains several connector client examples to help you utilize our framework for accelerated development against custom data sources.', - } - )} - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.button.label', - { - defaultMessage: 'Explore the connectors repository', - } - )} - - - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink', - { defaultMessage: 'connector client examples' } - )} - - ), - }} - /> - - - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink', - { defaultMessage: 'configuration file' } - )} - - ), - }} - /> - - - - {`${ - apiKeyData?.encoded - ? `elasticsearch: - api_key: "${apiKeyData?.encoded}" -` - : '' - }connector_id: "${indexData.connector.id}" -`} - - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorDeployedText', - { - defaultMessage: - 'Once you’ve configured the connector, deploy the connector to your self managed infrastructure.', - } - )} - - - {!indexData.connector.status || indexData.connector.status === ConnectorStatus.CREATED ? ( - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnectorText', - { - defaultMessage: - 'Your connector has not connected to Enterprise Search. Troubleshoot your configuration and refresh the page.', - } - )} - - recheckIndex()} - isLoading={recheckIndexLoading} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnector.button.label', - { - defaultMessage: 'Recheck now', - } - )} - - - ) : ( - - )} - - ); - return ( <> @@ -246,7 +80,137 @@ export const ConnectorConfiguration: React.FC = () => { titleSize: 'xs', }, { - children: , + children: ( + <> + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.firstParagraph', + { + defaultMessage: + 'The connectors repository contains several connector client examples to help you utilize our framework for accelerated development against custom data sources.', + } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.button.label', + { + defaultMessage: 'Explore the connectors repository', + } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink', + { defaultMessage: 'connector client examples' } + )} + + ), + }} + /> + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink', + { defaultMessage: 'configuration file' } + )} + + ), + }} + /> + + + + {`${ + apiKeyData?.encoded + ? `elasticsearch: + api_key: "${apiKeyData?.encoded}" + ` + : '' + }connector_id: "${indexData.connector.id}" + `} + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorDeployedText', + { + defaultMessage: + 'Once you’ve configured the connector, deploy the connector to your self managed infrastructure.', + } + )} + + + {!indexData.connector.status || + indexData.connector.status === ConnectorStatus.CREATED ? ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnectorText', + { + defaultMessage: + 'Your connector has not connected to Enterprise Search. Troubleshoot your configuration and refresh the page.', + } + )} + + recheckIndex()} + isLoading={recheckIndexLoading} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnector.button.label', + { + defaultMessage: 'Recheck now', + } + )} + + + ) : ( + + )} + + ), status: !indexData.connector.status || indexData.connector.status === ConnectorStatus.CREATED @@ -275,7 +239,40 @@ export const ConnectorConfiguration: React.FC = () => { titleSize: 'xs', }, { - children: , + children: ( + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.scheduleSync.description', + { + defaultMessage: + 'Once your connectors are configured to your liking, don’t forget to set a recurring sync schedule to make sure your documents are indexed and relevant. You can also trigger a one-time sync without enabling a sync schedule.', + } + )} + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.button.label', + { + defaultMessage: 'Set schedule and sync', + } + )} + + + + + + ), status: indexData.connector.scheduling.enabled ? 'complete' : 'incomplete', title: i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.schedule.title', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx index 472fb9570209e..5588a9c16fd5d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx @@ -35,28 +35,6 @@ export const ConnectorConfigurationConfig: React.FC = () => { title: label, })); - const display = ( - - - - - - - - setIsEditing(!isEditing)}> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.config.editButton.title', - { - defaultMessage: 'Edit configuration', - } - )} - - - - - - ); - return ( @@ -130,7 +108,31 @@ export const ConnectorConfigurationConfig: React.FC = () => { - {isEditing ? : displayList.length > 0 && display} + {isEditing ? ( + + ) : ( + displayList.length > 0 && ( + + + + + + + + setIsEditing(!isEditing)}> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.editButton.title', + { + defaultMessage: 'Edit configuration', + } + )} + + + + + + ) + )} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx index d972d7a1f3efa..b1fa1aaa378af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx @@ -28,20 +28,20 @@ import { import { IndexViewLogic } from '../index_view_logic'; import { SearchIndexTabId } from '../search_index'; +const StatusPanel: React.FC<{ ingestionStatus: IngestionStatus }> = ({ ingestionStatus }) => ( + + + +); + export const ConnectorOverviewPanels: React.FC = () => { const { ingestionStatus, index } = useValues(IndexViewLogic); - const statusPanel = ( - - - - ); - return isConnectorIndex(index) ? ( @@ -83,10 +83,10 @@ export const ConnectorOverviewPanels: React.FC = () => { tabId: SearchIndexTabId.CONFIGURATION, })} > - {statusPanel} + ) : ( - statusPanel + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx index 0977b64abb3f0..e6b74bb89773e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx @@ -60,50 +60,6 @@ export const SearchIndices: React.FC = () => { fetchIndices({ meta, returnHiddenIndices: showHiddenIndices, searchQuery }); }, [searchQuery, meta.page.current, showHiddenIndices]); - const createNewIndexButton = ( - - - {i18n.translate('xpack.enterpriseSearch.content.searchIndices.create.buttonTitle', { - defaultMessage: 'Create new index', - })} - - - ); - - const engineSteps = ( - <> - -

- {i18n.translate('xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle', { - defaultMessage: 'Build beautiful search experiences with Enterprise Search', - })} -

-
- - - - - - - - - - - ); - - const hiddenIndicesSwitch = ( -