From 05d1e326a4680fb55347396a7b0053c576726b18 Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Wed, 18 Aug 2021 07:37:14 -0700 Subject: [PATCH 01/36] [RAC][Security Solution] Remove ALERT_RULE_ID in favor of ALERT_RULE_UUID (#108922) * Remove ALERT_RULE_ID in favor of ALERT_RULE_UUID * Update snapshot * KEVINNN * fix test * Add back home.disableWelcomeScreen=true * Only disable welcome screen in security solution cypress tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../src/technical_field_names.ts | 3 --- .../assets/field_maps/technical_rule_field_map.ts | 5 ----- .../components/alerts_table/default_config.tsx | 2 -- .../get_signals_template.test.ts.snap | 8 ++++---- .../routes/index/signal_aad_mapping.json | 2 +- .../rule_registry_log_client.ts | 14 +++++++------- .../timeline/cases/add_to_case_action.test.tsx | 5 +++-- .../timelines/public/hooks/use_add_to_case.ts | 7 ++----- x-pack/test/security_solution_cypress/config.ts | 1 + 9 files changed, 18 insertions(+), 29 deletions(-) diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index 4b3f3fbb6f3705..2aa23195df8997 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -49,7 +49,6 @@ const ALERT_RULE_CREATED_BY = `${ALERT_RULE_NAMESPACE}.created_by` as const; const ALERT_RULE_DESCRIPTION = `${ALERT_RULE_NAMESPACE}.description` as const; const ALERT_RULE_ENABLED = `${ALERT_RULE_NAMESPACE}.enabled` as const; const ALERT_RULE_FROM = `${ALERT_RULE_NAMESPACE}.from` as const; -const ALERT_RULE_ID = `${ALERT_RULE_NAMESPACE}.id` as const; const ALERT_RULE_INTERVAL = `${ALERT_RULE_NAMESPACE}.interval` as const; const ALERT_RULE_LICENSE = `${ALERT_RULE_NAMESPACE}.license` as const; const ALERT_RULE_CATEGORY = `${ALERT_RULE_NAMESPACE}.category` as const; @@ -108,7 +107,6 @@ const fields = { ALERT_RULE_DESCRIPTION, ALERT_RULE_ENABLED, ALERT_RULE_FROM, - ALERT_RULE_ID, ALERT_RULE_INTERVAL, ALERT_RULE_LICENSE, ALERT_RULE_NAME, @@ -166,7 +164,6 @@ export { ALERT_RULE_DESCRIPTION, ALERT_RULE_ENABLED, ALERT_RULE_FROM, - ALERT_RULE_ID, ALERT_RULE_INTERVAL, ALERT_RULE_LICENSE, ALERT_RULE_NAME, diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index eb8d88cf697b93..f6566ee75920f9 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -95,11 +95,6 @@ export const technicalRuleFieldMap = { array: false, required: false, }, - [Fields.ALERT_RULE_ID]: { - type: 'keyword', - array: false, - required: false, - }, [Fields.ALERT_RULE_CREATED_AT]: { type: 'date', array: false, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 0519e3f2d4a750..75bd41037934b7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -13,7 +13,6 @@ import { ALERT_STATUS, ALERT_UUID, ALERT_RULE_UUID, - ALERT_RULE_ID, ALERT_RULE_NAME, ALERT_RULE_CATEGORY, } from '@kbn/rule-data-utils'; @@ -190,7 +189,6 @@ export const requiredFieldMappingsForActionsRuleRegistry = { 'alert.status': ALERT_STATUS, 'alert.duration.us': ALERT_DURATION, 'rule.uuid': ALERT_RULE_UUID, - 'rule.id': ALERT_RULE_ID, 'rule.name': ALERT_RULE_NAME, 'rule.category': ALERT_RULE_CATEGORY, producer: ALERT_RULE_PRODUCER, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap index b93fec8e99ca59..833a9084fdac6d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/__snapshots__/get_signals_template.test.ts.snap @@ -1838,10 +1838,6 @@ Object { "path": "signal.rule.from", "type": "alias", }, - "kibana.alert.rule.id": Object { - "path": "signal.rule.id", - "type": "alias", - }, "kibana.alert.rule.immutable": Object { "path": "signal.rule.immutable", "type": "alias", @@ -2034,6 +2030,10 @@ Object { "path": "signal.rule.updated_by", "type": "alias", }, + "kibana.alert.rule.uuid": Object { + "path": "signal.rule.id", + "type": "alias", + }, "kibana.alert.rule.version": Object { "path": "signal.rule.version", "type": "alias", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signal_aad_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signal_aad_mapping.json index 68c184b66c562d..8391d490162dfa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signal_aad_mapping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signal_aad_mapping.json @@ -35,7 +35,7 @@ "signal.rule.enabled": "kibana.alert.rule.enabled", "signal.rule.false_positives": "kibana.alert.rule.false_positives", "signal.rule.from": "kibana.alert.rule.from", - "signal.rule.id": "kibana.alert.rule.id", + "signal.rule.id": "kibana.alert.rule.uuid", "signal.rule.immutable": "kibana.alert.rule.immutable", "signal.rule.index": "kibana.alert.rule.index", "signal.rule.interval": "kibana.alert.rule.interval", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_registry_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_registry_log_client.ts index 3f2f34c17679f0..5445184c450fee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_registry_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_registry_log_client.ts @@ -13,7 +13,7 @@ import { EVENT_KIND, SPACE_IDS, TIMESTAMP, - ALERT_RULE_ID, + ALERT_RULE_UUID, } from '@kbn/rule-data-utils'; import moment from 'moment'; @@ -98,7 +98,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient { } const filter: estypes.QueryDslQueryContainer[] = [ - { terms: { [ALERT_RULE_ID]: ruleIds } }, + { terms: { [ALERT_RULE_UUID]: ruleIds } }, { terms: { [SPACE_IDS]: [spaceId] } }, ]; @@ -117,7 +117,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient { aggs: { rules: { terms: { - field: ALERT_RULE_ID, + field: ALERT_RULE_UUID, size: ruleIds.length, }, aggs: { @@ -151,7 +151,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient { bucket.most_recent_logs.hits.hits.map((event) => { const logEntry = parseRuleExecutionLog(event._source); invariant( - logEntry[ALERT_RULE_ID] ?? '', + logEntry[ALERT_RULE_UUID] ?? '', 'Malformed execution log entry: rule.id field not found' ); @@ -185,7 +185,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient { ] : undefined; - const alertId = logEntry[ALERT_RULE_ID] ?? ''; + const alertId = logEntry[ALERT_RULE_UUID] ?? ''; const statusDate = logEntry[TIMESTAMP]; const lastFailureAt = lastFailure?.[TIMESTAMP]; const lastFailureMessage = lastFailure?.[MESSAGE]; @@ -232,7 +232,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient { [EVENT_ACTION]: metric, [EVENT_KIND]: 'metric', [getMetricField(metric)]: value, - [ALERT_RULE_ID]: ruleId ?? '', + [ALERT_RULE_UUID]: ruleId ?? '', [TIMESTAMP]: new Date().toISOString(), [ALERT_RULE_CONSUMER]: SERVER_APP_ID, [ALERT_RULE_TYPE_ID]: SERVER_APP_ID, @@ -255,7 +255,7 @@ export class RuleRegistryLogClient implements IRuleRegistryLogClient { [EVENT_KIND]: 'event', [EVENT_SEQUENCE]: this.sequence++, [MESSAGE]: message, - [ALERT_RULE_ID]: ruleId ?? '', + [ALERT_RULE_UUID]: ruleId ?? '', [RULE_STATUS_SEVERITY]: statusSeverityDict[newStatus], [RULE_STATUS]: newStatus, [TIMESTAMP]: new Date().toISOString(), diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx index fb7899165bb3d9..19206c40d18c2d 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.test.tsx @@ -11,6 +11,7 @@ import { TestProviders, mockGetAllCasesSelectorModal } from '../../../../mock'; import { AddToCaseAction } from './add_to_case_action'; import { SECURITY_SOLUTION_OWNER } from '../../../../../../cases/common'; import { AddToCaseActionButton } from './add_to_case_action_button'; +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; jest.mock('react-router-dom', () => ({ useLocation: () => ({ @@ -100,7 +101,7 @@ describe('AddToCaseAction', () => { {...props} event={{ _id: 'test-id', - data: [{ field: 'kibana.alert.rule.id', value: ['rule-id'] }], + data: [{ field: ALERT_RULE_UUID, value: ['rule-id'] }], ecs: { _id: 'test-id', _index: 'test-index', @@ -112,7 +113,7 @@ describe('AddToCaseAction', () => { {...props} event={{ _id: 'test-id', - data: [{ field: 'kibana.alert.rule.id', value: ['rule-id'] }], + data: [{ field: ALERT_RULE_UUID, value: ['rule-id'] }], ecs: { _id: 'test-id', _index: 'test-index', diff --git a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts index a519c4869fa324..f5bb27b3a5614c 100644 --- a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts +++ b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts @@ -8,7 +8,7 @@ import { isEmpty } from 'lodash'; import { useState, useCallback, useMemo, SyntheticEvent } from 'react'; import { useLocation } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { ALERT_RULE_ID, ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { Case, SubCase } from '../../../cases/common'; import { TimelinesStartServices } from '../types'; @@ -243,15 +243,12 @@ export const useAddToCase = ({ }; export function normalizedEventFields(event?: TimelineItem) { - const ruleId = event && event.data.find(({ field }) => field === ALERT_RULE_ID); const ruleUuid = event && event.data.find(({ field }) => field === ALERT_RULE_UUID); const ruleName = event && event.data.find(({ field }) => field === ALERT_RULE_NAME); - const ruleIdValue = ruleId && ruleId.value && ruleId.value[0]; const ruleUuidValue = ruleUuid && ruleUuid.value && ruleUuid.value[0]; const ruleNameValue = ruleName && ruleName.value && ruleName.value[0]; - const idToUse = ruleIdValue ? ruleIdValue : ruleUuidValue; return { - ruleId: idToUse ?? null, + ruleId: ruleUuidValue ?? null, ruleName: ruleNameValue ?? null, }; } diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 0026f5897019e6..d22ff564beb2c8 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -40,6 +40,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // retrieve rules from the filesystem but not from fleet for Cypress tests '--xpack.securitySolution.prebuiltRulesFromFileSystem=true', '--xpack.securitySolution.prebuiltRulesFromSavedObjects=false', + `--home.disableWelcomeScreen=true`, ], }, }; From 92dcef9f26719602800bbc875ec274b24613bdb8 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 18 Aug 2021 17:04:58 +0200 Subject: [PATCH 02/36] Hide "Manage Searches" if insufficient permissions (#109099) --- .../open_search_panel.test.tsx.snap | 3 +- .../top_nav/open_search_panel.test.tsx | 26 +++++++++-- .../components/top_nav/open_search_panel.tsx | 45 +++++++++++-------- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/plugins/discover/public/application/apps/main/components/top_nav/__snapshots__/open_search_panel.test.tsx.snap b/src/plugins/discover/public/application/apps/main/components/top_nav/__snapshots__/open_search_panel.test.tsx.snap index 2c2674b158bfc1..6043a5d3825984 100644 --- a/src/plugins/discover/public/application/apps/main/components/top_nav/__snapshots__/open_search_panel.test.tsx.snap +++ b/src/plugins/discover/public/application/apps/main/components/top_nav/__snapshots__/open_search_panel.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`render 1`] = ` +exports[`OpenSearchPanel render 1`] = ` { return { getServices: () => ({ core: { uiSettings: {}, savedObjects: {} }, addBasePath: (path: string) => path, + capabilities: mockCapabilities(), }), }; }); import { OpenSearchPanel } from './open_search_panel'; -test('render', () => { - const component = shallow(); - expect(component).toMatchSnapshot(); +describe('OpenSearchPanel', () => { + test('render', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + test('should not render manage searches button without permissions', () => { + mockCapabilities.mockReturnValue({ + savedObjectsManagement: { + edit: false, + delete: false, + }, + }); + const component = shallow(); + expect(component.find('[data-test-subj="manageSearches"]').exists()).toBe(false); + }); }); diff --git a/src/plugins/discover/public/application/apps/main/components/top_nav/open_search_panel.tsx b/src/plugins/discover/public/application/apps/main/components/top_nav/open_search_panel.tsx index e63f010a8dffc1..31026a1e0ab594 100644 --- a/src/plugins/discover/public/application/apps/main/components/top_nav/open_search_panel.tsx +++ b/src/plugins/discover/public/application/apps/main/components/top_nav/open_search_panel.tsx @@ -34,8 +34,12 @@ export function OpenSearchPanel(props: OpenSearchPanelProps) { const { core: { uiSettings, savedObjects }, addBasePath, + capabilities, } = getServices(); + const hasSavedObjectPermission = + capabilities.savedObjectsManagement?.edit || capabilities.savedObjectsManagement?.delete; + return ( @@ -73,25 +77,28 @@ export function OpenSearchPanel(props: OpenSearchPanelProps) { savedObjects={savedObjects} /> - - - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - - - - - + {hasSavedObjectPermission && ( + + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + + + + )} ); } From 6a1a1afd3a891d8b7a9dc53e3fd29fc2ebac9241 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 18 Aug 2021 17:08:42 +0200 Subject: [PATCH 03/36] [Data cleanup] Remove geo filters (#109097) * Remove geo bounding box and geo polygon filters * delete geo filters * delete geo filters * geo * remove geo docs --- ...na-plugin-plugins-data-public.esfilters.md | 2 +- .../geo_bounding_box_filter.test.ts | 36 -------- .../build_filters/geo_bounding_box_filter.ts | 41 --------- .../build_filters/geo_polygon_filter.test.ts | 34 -------- .../build_filters/geo_polygon_filter.ts | 39 --------- .../filters/build_filters/get_filter_field.ts | 8 -- .../src/filters/build_filters/index.ts | 2 - .../src/filters/build_filters/types.ts | 6 -- packages/kbn-es-query/src/filters/index.ts | 4 - src/plugins/data/common/es_query/index.ts | 32 ------- src/plugins/data/public/public.api.md | 2 +- .../query/filter_manager/lib/map_filter.ts | 4 - .../lib/mappers/map_geo_bounding_box.test.ts | 85 ------------------- .../lib/mappers/map_geo_bounding_box.ts | 47 ---------- .../lib/mappers/map_geo_polygon.test.ts | 77 ----------------- .../lib/mappers/map_geo_polygon.ts | 40 --------- .../filter_editor/lib/filter_label.tsx | 14 --- 17 files changed, 2 insertions(+), 471 deletions(-) delete mode 100644 packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.test.ts delete mode 100644 packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.ts delete mode 100644 packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.test.ts delete mode 100644 packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.ts delete mode 100644 src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts delete mode 100644 src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts delete mode 100644 src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts delete mode 100644 src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md index b5d5c5cfee5ada..2500ed9b2bc05e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md @@ -28,7 +28,7 @@ esFilters: { isPhraseFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").PhraseFilter; isExistsFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").ExistsFilter; isPhrasesFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").PhrasesFilter; - isRangeFilter: (filter?: import("@kbn/es-query").ExistsFilter | import("@kbn/es-query").GeoPolygonFilter | import("@kbn/es-query").PhrasesFilter | import("@kbn/es-query").PhraseFilter | import("@kbn/es-query").MatchAllFilter | import("@kbn/es-query").MissingFilter | import("@kbn/es-query").RangeFilter | import("@kbn/es-query").GeoBoundingBoxFilter | undefined) => filter is import("@kbn/es-query").RangeFilter; + isRangeFilter: (filter?: import("@kbn/es-query").ExistsFilter | import("@kbn/es-query").PhrasesFilter | import("@kbn/es-query").PhraseFilter | import("@kbn/es-query").MatchAllFilter | import("@kbn/es-query").MissingFilter | import("@kbn/es-query").RangeFilter | undefined) => filter is import("@kbn/es-query").RangeFilter; isMatchAllFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").MatchAllFilter; isMissingFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").MissingFilter; isQueryStringFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query/target_types/filters/build_filters").QueryStringFilter; diff --git a/packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.test.ts b/packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.test.ts deleted file mode 100644 index af42e3f2c73fb8..00000000000000 --- a/packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getGeoBoundingBoxFilterField } from './geo_bounding_box_filter'; - -describe('geo_bounding_box filter', function () { - describe('getGeoBoundingBoxFilterField', function () { - it('should return the name of the field a geo_bounding_box query is targeting', () => { - const filter = { - geo_bounding_box: { - geoPointField: { - bottom_right: { lat: 1, lon: 1 }, - top_left: { lat: 1, lon: 1 }, - }, - ignore_unmapped: true, - }, - meta: { - disabled: false, - negate: false, - alias: null, - params: { - bottom_right: { lat: 1, lon: 1 }, - top_left: { lat: 1, lon: 1 }, - }, - }, - }; - const result = getGeoBoundingBoxFilterField(filter); - expect(result).toBe('geoPointField'); - }); - }); -}); diff --git a/packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.ts b/packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.ts deleted file mode 100644 index 9066b695c17fc8..00000000000000 --- a/packages/kbn-es-query/src/filters/build_filters/geo_bounding_box_filter.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { has } from 'lodash'; -import type { FieldFilter, Filter, FilterMeta, LatLon } from './types'; - -export type GeoBoundingBoxFilterMeta = FilterMeta & { - params: { - bottom_right: LatLon; - top_left: LatLon; - }; -}; - -export type GeoBoundingBoxFilter = Filter & { - meta: GeoBoundingBoxFilterMeta; - geo_bounding_box: any; -}; - -/** - * @param filter - * @returns `true` if a filter is an `GeoBoundingBoxFilter` - * - * @public - */ -export const isGeoBoundingBoxFilter = (filter: FieldFilter): filter is GeoBoundingBoxFilter => - has(filter, 'geo_bounding_box'); - -/** - * @internal - */ -export const getGeoBoundingBoxFilterField = (filter: GeoBoundingBoxFilter) => { - return ( - filter.geo_bounding_box && - Object.keys(filter.geo_bounding_box).find((key) => key !== 'ignore_unmapped') - ); -}; diff --git a/packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.test.ts b/packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.test.ts deleted file mode 100644 index 919ccbe427cd69..00000000000000 --- a/packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getGeoPolygonFilterField } from './geo_polygon_filter'; - -describe('geo_polygon filter', function () { - describe('getGeoPolygonFilterField', function () { - it('should return the name of the field a geo_polygon query is targeting', () => { - const filter = { - geo_polygon: { - geoPointField: { - points: [{ lat: 1, lon: 1 }], - }, - ignore_unmapped: true, - }, - meta: { - disabled: false, - negate: false, - alias: null, - params: { - points: [{ lat: 1, lon: 1 }], - }, - }, - }; - const result = getGeoPolygonFilterField(filter); - expect(result).toBe('geoPointField'); - }); - }); -}); diff --git a/packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.ts b/packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.ts deleted file mode 100644 index edeccdcf28b261..00000000000000 --- a/packages/kbn-es-query/src/filters/build_filters/geo_polygon_filter.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { has } from 'lodash'; -import type { FieldFilter, Filter, FilterMeta, LatLon } from './types'; - -export type GeoPolygonFilterMeta = FilterMeta & { - params: { - points: LatLon[]; - }; -}; - -export type GeoPolygonFilter = Filter & { - meta: GeoPolygonFilterMeta; - geo_polygon: any; -}; - -/** - * @param filter - * @returns `true` if a filter is an `GeoPolygonFilter` - * - * @public - */ -export const isGeoPolygonFilter = (filter: FieldFilter): filter is GeoPolygonFilter => - has(filter, 'geo_polygon'); - -/** - * @internal - */ -export const getGeoPolygonFilterField = (filter: GeoPolygonFilter) => { - return ( - filter.geo_polygon && Object.keys(filter.geo_polygon).find((key) => key !== 'ignore_unmapped') - ); -}; diff --git a/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts b/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts index 4ebed12e1237ec..70949be18a61fc 100644 --- a/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts +++ b/packages/kbn-es-query/src/filters/build_filters/get_filter_field.ts @@ -7,8 +7,6 @@ */ import { getExistsFilterField, isExistsFilter } from './exists_filter'; -import { getGeoBoundingBoxFilterField, isGeoBoundingBoxFilter } from './geo_bounding_box_filter'; -import { getGeoPolygonFilterField, isGeoPolygonFilter } from './geo_polygon_filter'; import { getMissingFilterField, isMissingFilter } from './missing_filter'; import { getPhrasesFilterField, isPhrasesFilter } from './phrases_filter'; import { getPhraseFilterField, isPhraseFilter } from './phrase_filter'; @@ -20,12 +18,6 @@ export const getFilterField = (filter: Filter) => { if (isExistsFilter(filter)) { return getExistsFilterField(filter); } - if (isGeoBoundingBoxFilter(filter)) { - return getGeoBoundingBoxFilterField(filter); - } - if (isGeoPolygonFilter(filter)) { - return getGeoPolygonFilterField(filter); - } if (isPhraseFilter(filter)) { return getPhraseFilterField(filter); } diff --git a/packages/kbn-es-query/src/filters/build_filters/index.ts b/packages/kbn-es-query/src/filters/build_filters/index.ts index c8b96d8b8efc9a..7f81d83e6627de 100644 --- a/packages/kbn-es-query/src/filters/build_filters/index.ts +++ b/packages/kbn-es-query/src/filters/build_filters/index.ts @@ -11,8 +11,6 @@ export * from './build_filters'; export * from './build_empty_filter'; export * from './custom_filter'; export * from './exists_filter'; -export * from './geo_bounding_box_filter'; -export * from './geo_polygon_filter'; export * from './get_filter_field'; export * from './get_filter_params'; export * from './match_all_filter'; diff --git a/packages/kbn-es-query/src/filters/build_filters/types.ts b/packages/kbn-es-query/src/filters/build_filters/types.ts index 4bf53303c15441..5aad7d8735f8c7 100644 --- a/packages/kbn-es-query/src/filters/build_filters/types.ts +++ b/packages/kbn-es-query/src/filters/build_filters/types.ts @@ -7,8 +7,6 @@ */ import { ExistsFilter } from './exists_filter'; -import { GeoBoundingBoxFilter } from './geo_bounding_box_filter'; -import { GeoPolygonFilter } from './geo_polygon_filter'; import { PhrasesFilter } from './phrases_filter'; import { PhraseFilter } from './phrase_filter'; import { RangeFilter } from './range_filter'; @@ -21,8 +19,6 @@ import { MissingFilter } from './missing_filter'; **/ export type FieldFilter = | ExistsFilter - | GeoBoundingBoxFilter - | GeoPolygonFilter | PhraseFilter | PhrasesFilter | RangeFilter @@ -49,8 +45,6 @@ export enum FILTERS { QUERY_STRING = 'query_string', RANGE = 'range', RANGE_FROM_VALUE = 'range_from_value', - GEO_BOUNDING_BOX = 'geo_bounding_box', - GEO_POLYGON = 'geo_polygon', SPATIAL_FILTER = 'spatial_filter', } diff --git a/packages/kbn-es-query/src/filters/index.ts b/packages/kbn-es-query/src/filters/index.ts index 61ee6ce6f0da6c..6e82c21b6a529d 100644 --- a/packages/kbn-es-query/src/filters/index.ts +++ b/packages/kbn-es-query/src/filters/index.ts @@ -30,8 +30,6 @@ export { export { isExistsFilter, isMatchAllFilter, - isGeoBoundingBoxFilter, - isGeoPolygonFilter, isMissingFilter, isPhraseFilter, isPhrasesFilter, @@ -67,11 +65,9 @@ export { PhraseFilter, PhrasesFilter, RangeFilterMeta, - GeoPolygonFilter, MatchAllFilter, CustomFilter, MissingFilter, - GeoBoundingBoxFilter, RangeFilterParams, } from './build_filters'; diff --git a/src/plugins/data/common/es_query/index.ts b/src/plugins/data/common/es_query/index.ts index ee98a9ecf88927..6d84b3fd6eab48 100644 --- a/src/plugins/data/common/es_query/index.ts +++ b/src/plugins/data/common/es_query/index.ts @@ -29,8 +29,6 @@ import { isFilters as oldIsFilters, isExistsFilter as oldIsExistsFilter, isMatchAllFilter as oldIsMatchAllFilter, - isGeoBoundingBoxFilter as oldIsGeoBoundingBoxFilter, - isGeoPolygonFilter as oldIsGeoPolygonFilter, isMissingFilter as oldIsMissingFilter, isPhraseFilter as oldIsPhraseFilter, isPhrasesFilter as oldIsPhrasesFilter, @@ -49,14 +47,12 @@ import { RangeFilterMeta as oldRangeFilterMeta, RangeFilterParams as oldRangeFilterParams, ExistsFilter as oldExistsFilter, - GeoPolygonFilter as oldGeoPolygonFilter, PhrasesFilter as oldPhrasesFilter, PhraseFilter as oldPhraseFilter, MatchAllFilter as oldMatchAllFilter, CustomFilter as oldCustomFilter, MissingFilter as oldMissingFilter, RangeFilter as oldRangeFilter, - GeoBoundingBoxFilter as oldGeoBoundingBoxFilter, KueryNode as oldKueryNode, FilterMeta as oldFilterMeta, FILTERS as oldFILTERS, @@ -176,18 +172,6 @@ const isExistsFilter = oldIsExistsFilter; */ const isMatchAllFilter = oldIsMatchAllFilter; -/** - * @deprecated Import from the "@kbn/es-query" package directly instead. - * @removeBy 8.1 - */ -const isGeoBoundingBoxFilter = oldIsGeoBoundingBoxFilter; - -/** - * @deprecated Import from the "@kbn/es-query" package directly instead. - * @removeBy 8.1 - */ -const isGeoPolygonFilter = oldIsGeoPolygonFilter; - /** * @deprecated Import from the "@kbn/es-query" package directly instead. * @removeBy 8.1 @@ -338,12 +322,6 @@ type RangeFilterParams = oldRangeFilterParams; */ type ExistsFilter = oldExistsFilter; -/** - * @deprecated Import from the "@kbn/es-query" package directly instead. - * @removeBy 8.1 - */ -type GeoPolygonFilter = oldGeoPolygonFilter; - /** * @deprecated Import from the "@kbn/es-query" package directly instead. * @removeBy 8.1 @@ -380,12 +358,6 @@ type MissingFilter = oldMissingFilter; */ type RangeFilter = oldRangeFilter; -/** - * @deprecated Import from the "@kbn/es-query" package directly instead. - * @removeBy 8.1 - */ -type GeoBoundingBoxFilter = oldGeoBoundingBoxFilter; - /** * @deprecated Import from the "@kbn/es-query" package directly instead. * @removeBy 8.1 @@ -436,8 +408,6 @@ export { isFilters, isExistsFilter, isMatchAllFilter, - isGeoBoundingBoxFilter, - isGeoPolygonFilter, isMissingFilter, isPhraseFilter, isPhrasesFilter, @@ -463,14 +433,12 @@ export { RangeFilterMeta, RangeFilterParams, ExistsFilter, - GeoPolygonFilter, PhrasesFilter, PhraseFilter, MatchAllFilter, CustomFilter, MissingFilter, RangeFilter, - GeoBoundingBoxFilter, KueryNode, FilterMeta, IFieldSubType, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index c6f8100c484135..9dd7dff9e5b666 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -748,7 +748,7 @@ export const esFilters: { isPhraseFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").PhraseFilter; isExistsFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").ExistsFilter; isPhrasesFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").PhrasesFilter; - isRangeFilter: (filter?: import("@kbn/es-query").ExistsFilter | import("@kbn/es-query").GeoPolygonFilter | import("@kbn/es-query").PhrasesFilter | import("@kbn/es-query").PhraseFilter | import("@kbn/es-query").MatchAllFilter | import("@kbn/es-query").MissingFilter | import("@kbn/es-query").RangeFilter | import("@kbn/es-query").GeoBoundingBoxFilter | undefined) => filter is import("@kbn/es-query").RangeFilter; + isRangeFilter: (filter?: import("@kbn/es-query").ExistsFilter | import("@kbn/es-query").PhrasesFilter | import("@kbn/es-query").PhraseFilter | import("@kbn/es-query").MatchAllFilter | import("@kbn/es-query").MissingFilter | import("@kbn/es-query").RangeFilter | undefined) => filter is import("@kbn/es-query").RangeFilter; isMatchAllFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").MatchAllFilter; isMissingFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query").MissingFilter; isQueryStringFilter: (filter: import("@kbn/es-query").FieldFilter) => filter is import("@kbn/es-query/target_types/filters/build_filters").QueryStringFilter; diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts index 249c7bf47b8fb5..d5e5d922d19d54 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts @@ -17,8 +17,6 @@ import { mapRange } from './mappers/map_range'; import { mapExists } from './mappers/map_exists'; import { mapMissing } from './mappers/map_missing'; import { mapQueryString } from './mappers/map_query_string'; -import { mapGeoBoundingBox } from './mappers/map_geo_bounding_box'; -import { mapGeoPolygon } from './mappers/map_geo_polygon'; import { mapDefault } from './mappers/map_default'; import { generateMappingChain } from './generate_mapping_chain'; @@ -48,8 +46,6 @@ export function mapFilter(filter: Filter) { mapExists, mapMissing, mapQueryString, - mapGeoBoundingBox, - mapGeoPolygon, mapDefault, ]; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts deleted file mode 100644 index aca6a345cb97ea..00000000000000 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { mapGeoBoundingBox } from './map_geo_bounding_box'; -import { Filter, GeoBoundingBoxFilter } from '../../../../../common'; - -describe('filter manager utilities', () => { - describe('mapGeoBoundingBox()', () => { - test('should return the key and value for matching filters with bounds', async () => { - const filter = { - meta: { - index: 'logstash-*', - }, - geo_bounding_box: { - point: { - // field name - top_left: { lat: 5, lon: 10 }, - bottom_right: { lat: 15, lon: 20 }, - }, - }, - } as GeoBoundingBoxFilter; - - const result = mapGeoBoundingBox(filter); - - expect(result).toHaveProperty('key', 'point'); - expect(result).toHaveProperty('value'); - - if (result.value) { - const displayName = result.value(); - // remove html entities and non-alphanumerics to get the gist of the value - expect(displayName.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( - 'lat5lon10tolat15lon20' - ); - } - }); - - test('should return the key and value even when using ignore_unmapped', async () => { - const filter = { - meta: { - index: 'logstash-*', - }, - geo_bounding_box: { - ignore_unmapped: true, - point: { - // field name - top_left: { lat: 5, lon: 10 }, - bottom_right: { lat: 15, lon: 20 }, - }, - }, - } as GeoBoundingBoxFilter; - - const result = mapGeoBoundingBox(filter); - - expect(result).toHaveProperty('key', 'point'); - expect(result).toHaveProperty('value'); - - if (result.value) { - const displayName = result.value(); - // remove html entities and non-alphanumerics to get the gist of the value - expect(displayName.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( - 'lat5lon10tolat15lon20' - ); - } - }); - - test('should return undefined for none matching', async (done) => { - const filter = { - meta: { index: 'logstash-*' }, - query: { query_string: { query: 'foo:bar' } }, - } as Filter; - - try { - mapGeoBoundingBox(filter); - } catch (e) { - expect(e).toBe(filter); - done(); - } - }); - }); -}); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts deleted file mode 100644 index dfa8e862e1b4c4..00000000000000 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { GeoBoundingBoxFilter, FILTERS, isGeoBoundingBoxFilter, Filter } from '@kbn/es-query'; - -import { FilterValueFormatter } from '../../../../../common'; - -const getFormattedValueFn = (params: any) => { - return (formatter?: FilterValueFormatter) => { - const corners = formatter - ? { - topLeft: formatter.convert(params.top_left), - bottomRight: formatter.convert(params.bottom_right), - } - : { - topLeft: JSON.stringify(params.top_left), - bottomRight: JSON.stringify(params.bottom_right), - }; - - return corners.topLeft + ' to ' + corners.bottomRight; - }; -}; - -const getParams = (filter: GeoBoundingBoxFilter) => { - const key = Object.keys(filter.geo_bounding_box).filter((k) => k !== 'ignore_unmapped')[0]; - const params = filter.geo_bounding_box[key]; - - return { - key, - params, - type: FILTERS.GEO_BOUNDING_BOX, - value: getFormattedValueFn(params), - }; -}; - -export const mapGeoBoundingBox = (filter: Filter) => { - if (!isGeoBoundingBoxFilter(filter)) { - throw filter; - } - - return getParams(filter); -}; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts deleted file mode 100644 index c8fba3adcbb850..00000000000000 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { mapGeoPolygon } from './map_geo_polygon'; -import { Filter, GeoPolygonFilter } from '../../../../../common'; - -describe('filter manager utilities', () => { - let filter: GeoPolygonFilter; - - beforeEach(() => { - filter = { - meta: { - index: 'logstash-*', - }, - geo_polygon: { - point: { - points: [ - { lat: 5, lon: 10 }, - { lat: 15, lon: 20 }, - ], - }, - }, - } as GeoPolygonFilter; - }); - - describe('mapGeoPolygon()', () => { - test('should return the key and value for matching filters with bounds', async () => { - const result = mapGeoPolygon(filter); - - expect(result).toHaveProperty('key', 'point'); - expect(result).toHaveProperty('value'); - - if (result.value) { - const displayName = result.value(); - // remove html entities and non-alphanumerics to get the gist of the value - expect(displayName.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( - 'lat5lon10lat15lon20' - ); - } - }); - - test('should return the key and value even when using ignore_unmapped', async () => { - const result = mapGeoPolygon(filter); - - expect(result).toHaveProperty('key', 'point'); - expect(result).toHaveProperty('value'); - - if (result.value) { - const displayName = result.value(); - // remove html entities and non-alphanumerics to get the gist of the value - expect(displayName.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( - 'lat5lon10lat15lon20' - ); - } - }); - - test('should return undefined for none matching', async (done) => { - const wrongFilter = { - meta: { index: 'logstash-*' }, - query: { query_string: { query: 'foo:bar' } }, - } as Filter; - - try { - mapGeoPolygon(wrongFilter); - } catch (e) { - expect(e).toBe(wrongFilter); - - done(); - } - }); - }); -}); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts deleted file mode 100644 index e1c21aae64da66..00000000000000 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { GeoPolygonFilter, FILTERS, Filter, isGeoPolygonFilter } from '@kbn/es-query'; - -import { FilterValueFormatter } from '../../../../../common'; - -const POINTS_SEPARATOR = ', '; - -const getFormattedValueFn = (points: string[]) => { - return (formatter?: FilterValueFormatter) => { - return points - .map((point: string) => (formatter ? formatter.convert(point) : JSON.stringify(point))) - .join(POINTS_SEPARATOR); - }; -}; - -function getParams(filter: GeoPolygonFilter) { - const key = Object.keys(filter.geo_polygon).filter((k) => k !== 'ignore_unmapped')[0]; - const params = filter.geo_polygon[key]; - - return { - key, - params, - type: FILTERS.GEO_POLYGON, - value: getFormattedValueFn(params.points || []), - }; -} - -export function mapGeoPolygon(filter: Filter) { - if (!isGeoPolygonFilter(filter)) { - throw filter; - } - return getParams(filter); -} diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx index 5055e122e199aa..6debf34c2ae8c2 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_label.tsx @@ -57,20 +57,6 @@ export default function FilterLabel({ filter, valueLabel, filterLabelStatus }: F {filter.meta.key}: {getValue(`${existsOperator.message}`)} ); - case FILTERS.GEO_BOUNDING_BOX: - return ( - - {prefix} - {filter.meta.key}: {getValue(valueLabel)} - - ); - case FILTERS.GEO_POLYGON: - return ( - - {prefix} - {filter.meta.key}: {getValue(valueLabel)} - - ); case FILTERS.PHRASES: return ( From a2b8b22929ece67dec377a2304c2d609e08f6072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Wed, 18 Aug 2021 11:14:52 -0400 Subject: [PATCH 04/36] [CTI] bring back skipped cypress tests (#108978) --- .../detection_alerts/cti_enrichments.spec.ts | 3 +-- .../detection_rules/indicator_match_rule.spec.ts | 14 +++++--------- .../security_solution/cypress/screens/alerts.ts | 10 +++------- .../cypress/screens/rule_details.ts | 2 +- .../cypress/tasks/create_new_rule.ts | 6 +++--- .../truncatable_text/truncatable_text.tsx | 14 +++++++++++--- .../timeline/body/renderers/formatted_field.tsx | 4 ++-- .../body/renderers/formatted_field_helpers.tsx | 6 +++++- 8 files changed, 31 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts index 221fd4ae3266e6..8ce3de6e5d7ac1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts @@ -37,8 +37,7 @@ import { import { ALERTS_URL } from '../../urls/navigation'; import { addsFieldsToTimeline } from '../../tasks/rule_details'; -// TODO: Doesn't look like the roll over is happening for these tests. 'indicator' is still referenced in the fields browser -describe.skip('CTI Enrichment', () => { +describe('CTI Enrichment', () => { before(() => { cleanKibana(); esArchiverLoad('threat_indicator'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index 6b8afc5da49495..f8b3b426580b29 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -116,7 +116,7 @@ describe('indicator match', () => { const expectedTags = getNewThreatIndicatorRule().tags.join(''); const expectedMitre = formatMitreAttackDescription(getNewThreatIndicatorRule().mitre); const expectedNumberOfRules = 1; - const expectedNumberOfAlerts = 1; + const expectedNumberOfAlerts = '1 alert'; before(() => { cleanKibana(); @@ -139,8 +139,7 @@ describe('indicator match', () => { getIndicatorIndex().should('have.text', getIndexPatterns().join('')); }); - // TODO: Need to fix - it.skip('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { + it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { getDefineContinueButton().click(); getIndexPatternInvalidationText().should('not.exist'); }); @@ -154,8 +153,7 @@ describe('indicator match', () => { }); describe('Indicator index patterns', () => { - // TODO: Need to fix - it.skip('Contains a predefined index pattern', () => { + it('Contains a predefined index pattern', () => { getIndicatorIndicatorIndex().should('have.text', getThreatIndexPatterns().join('')); }); @@ -392,8 +390,7 @@ describe('indicator match', () => { loginAndWaitForPageWithoutDateRange(ALERTS_URL); }); - // TODO: Need to fix - it.skip('Creates and activates a new Indicator Match rule', () => { + it('Creates and activates a new Indicator Match rule', () => { waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); @@ -491,8 +488,7 @@ describe('indicator match', () => { .should('have.text', getNewThreatIndicatorRule().riskScore); }); - // TODO: Need to fix - it.skip('Investigate alert in timeline', () => { + it('Investigate alert in timeline', () => { const accessibilityText = `Press enter for options, or press space to begin dragging.`; loadPrepackagedTimelineTemplates(); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/cypress/screens/alerts.ts index 83b3eb8a613cf2..71f7184230f2be 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts.ts @@ -21,15 +21,11 @@ export const ALERT_ID = '[data-test-subj="draggable-content-_id"]'; export const ALERT_RISK_SCORE_HEADER = '[data-test-subj="dataGridHeaderCell-signal.rule.risk_score"]'; -export const ALERT_RULE_METHOD = '[data-test-subj="draggable-content-signal.rule.type"]'; +export const ALERT_RULE_NAME = '[data-test-subj="formatted-field-signal.rule.name"]'; -export const ALERT_RULE_NAME = '[data-test-subj="draggable-content-signal.rule.name"]'; +export const ALERT_RULE_RISK_SCORE = '[data-test-subj="formatted-field-signal.rule.risk_score"]'; -export const ALERT_RULE_RISK_SCORE = '[data-test-subj="draggable-content-signal.rule.risk_score"]'; - -export const ALERT_RULE_SEVERITY = '[data-test-subj="draggable-content-signal.rule.severity"]'; - -export const ALERT_RULE_VERSION = '[data-test-subj="draggable-content-signal.rule.version"]'; +export const ALERT_RULE_SEVERITY = '[data-test-subj="formatted-field-signal.rule.severity"]'; export const CLOSE_ALERT_BTN = '[data-test-subj="close-alert-status"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index d94be17a0530a0..9bc22f35741d9b 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -96,7 +96,7 @@ export const TIMELINE_TEMPLATE_DETAILS = 'Timeline template'; export const TIMESTAMP_OVERRIDE_DETAILS = 'Timestamp override'; export const TIMELINE_FIELD = (field: string) => { - return `[data-test-subj="draggable-content-${field}"]`; + return `[data-test-subj="formatted-field-${field}"]`; }; export const getDetails = (title: string) => diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 47225227485f0e..9a500f81ec45f2 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -394,7 +394,7 @@ export const fillIndexAndIndicatorIndexPattern = ( ) => { getIndexPatternClearButton().click(); getIndicatorIndex().type(`${indexPattern}{enter}`); - getIndicatorIndicatorIndex().type(`${indicatorIndex}{enter}`); + getIndicatorIndicatorIndex().type(`{backspace}{enter}${indicatorIndex}{enter}`); }; export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConnector()) => { @@ -437,7 +437,7 @@ export const getIndexPatternInvalidationText = () => cy.contains(AT_LEAST_ONE_IN export const getAboutContinueButton = () => cy.get(ABOUT_CONTINUE_BTN); /** Returns the continue button on the step of define */ -export const getDefineContinueButton = () => cy.get(DEFINE_CONTINUE_BUTTON); +export const getDefineContinueButton = () => cy.get(DEFINE_CONTINUE_BUTTON).should('exist'); /** Returns the indicator index pattern */ export const getIndicatorIndex = () => cy.get(THREAT_MATCH_INDICATOR_INDEX).eq(0); @@ -447,7 +447,7 @@ export const getIndicatorIndicatorIndex = () => cy.get(THREAT_MATCH_INDICATOR_INDICATOR_INDEX).eq(0); /** Returns the index pattern's clear button */ -export const getIndexPatternClearButton = () => cy.get(COMBO_BOX_CLEAR_BTN).first(); +export const getIndexPatternClearButton = () => cy.get(COMBO_BOX_CLEAR_BTN).should('exist').first(); /** Returns the custom query input */ export const getCustomQueryInput = () => cy.get(THREAT_MATCH_CUSTOM_QUERY_INPUT).eq(0); diff --git a/x-pack/plugins/security_solution/public/common/components/truncatable_text/truncatable_text.tsx b/x-pack/plugins/security_solution/public/common/components/truncatable_text/truncatable_text.tsx index 4c068675aa5a00..cc1c53d1071002 100644 --- a/x-pack/plugins/security_solution/public/common/components/truncatable_text/truncatable_text.tsx +++ b/x-pack/plugins/security_solution/public/common/components/truncatable_text/truncatable_text.tsx @@ -32,14 +32,22 @@ EllipsisText.displayName = 'EllipsisText'; interface Props { tooltipContent?: React.ReactNode; children: React.ReactNode; + dataTestSubj?: string; } -export function TruncatableText({ tooltipContent, children, ...props }: Props) { - if (!tooltipContent) return {children}; +export function TruncatableText({ tooltipContent, children, dataTestSubj, ...props }: Props) { + if (!tooltipContent) + return ( + + {children} + + ); return ( - {children} + + {children} + ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 06ed9011109625..5eec4ef66f39c2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -202,11 +202,11 @@ const FormattedFieldValueComponent: React.FC<{ } > - <>{value} + {value} ) : ( - <>{value} + {value} ); } else { const contentValue = getOrEmptyTagFromValue(value); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx index c7e15dae0035ee..3343882e0071a8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx @@ -52,7 +52,11 @@ export const RenderRuleName: React.FC = ({ const ruleId = linkValue; const { search } = useFormatUrl(SecurityPageName.rules); const { navigateToApp, getUrlForApp } = useKibana().services.application; - const content = truncate ? {value} : value; + const content = truncate ? ( + {value} + ) : ( + value + ); const goToRuleDetails = useCallback( (ev) => { From ff58841d899598166330eff2cf5c8b6c83b41cac Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Wed, 18 Aug 2021 17:17:53 +0200 Subject: [PATCH 05/36] added defaultColumns property to default security solutions timeline (#109086) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/common/mock/global_state.ts | 1 + .../public/common/mock/timeline_results.ts | 98 ++++++++++--------- .../timelines/store/timeline/defaults.ts | 1 + .../timelines/store/timeline/epic.test.ts | 86 ++++++++-------- .../public/timelines/store/timeline/model.ts | 1 + .../timelines/store/timeline/reducer.test.ts | 1 + .../timelines/public/store/t_grid/defaults.ts | 1 + .../timelines/public/store/t_grid/model.ts | 2 + 8 files changed, 102 insertions(+), 89 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 8130a7058700db..fb772986bc6799 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -245,6 +245,7 @@ export const mockGlobalState: State = { id: 'test', savedObjectId: null, columns: defaultHeaders, + defaultColumns: defaultHeaders, indexNames: DEFAULT_INDEX_PATTERN, itemsPerPage: 5, dataProviders: [], diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index f271f49e56a292..e67b61664745ee 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -1930,46 +1930,48 @@ export const mockTimelineResults: OpenTimelineResult[] = [ }, ]; +const mockTimelineModelColumns: TimelineModel['columns'] = [ + { + columnHeaderType: 'not-filtered', + id: '@timestamp', + initialWidth: 190, + }, + { + columnHeaderType: 'not-filtered', + id: 'message', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'event.category', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'host.name', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'source.ip', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'destination.ip', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'user.name', + initialWidth: 180, + }, +]; export const mockTimelineModel: TimelineModel = { activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.notes, - columns: [ - { - columnHeaderType: 'not-filtered', - id: '@timestamp', - initialWidth: 190, - }, - { - columnHeaderType: 'not-filtered', - id: 'message', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'event.category', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'host.name', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'source.ip', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'destination.ip', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'user.name', - initialWidth: 180, - }, - ], + columns: mockTimelineModelColumns, + defaultColumns: mockTimelineModelColumns, dataProviders: [], dateRange: { end: '2020-03-18T13:52:38.929Z', @@ -2076,21 +2078,23 @@ export const mockTimelineResult = { stale: false, }; +const defaultTimelineColumns: CreateTimelineProps['timeline']['columns'] = [ + { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', initialWidth: 190 }, + { columnHeaderType: 'not-filtered', id: 'message', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'event.category', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'event.action', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'host.name', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'source.ip', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'destination.ip', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'user.name', initialWidth: 180 }, +]; export const defaultTimelineProps: CreateTimelineProps = { from: '2018-11-05T18:58:25.937Z', timeline: { activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.query, - columns: [ - { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', initialWidth: 190 }, - { columnHeaderType: 'not-filtered', id: 'message', initialWidth: 180 }, - { columnHeaderType: 'not-filtered', id: 'event.category', initialWidth: 180 }, - { columnHeaderType: 'not-filtered', id: 'event.action', initialWidth: 180 }, - { columnHeaderType: 'not-filtered', id: 'host.name', initialWidth: 180 }, - { columnHeaderType: 'not-filtered', id: 'source.ip', initialWidth: 180 }, - { columnHeaderType: 'not-filtered', id: 'destination.ip', initialWidth: 180 }, - { columnHeaderType: 'not-filtered', id: 'user.name', initialWidth: 180 }, - ], + columns: defaultTimelineColumns, + defaultColumns: defaultTimelineColumns, dataProviders: [ { and: [], diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts index d8fd82005dfbed..f411c6ffac9b7e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts @@ -19,6 +19,7 @@ export const timelineDefaults: SubsetTimelineModel & activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.query, columns: defaultHeaders, + defaultColumns: defaultHeaders, dataProviders: [], dateRange: { start, end }, deletedEventIds: [], diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts index eabcdd53fb9941..8b40febbfe993a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts @@ -14,51 +14,53 @@ import { TimelineModel } from './model'; describe('Epic Timeline', () => { describe('#convertTimelineAsInput ', () => { test('should return a TimelineInput instead of TimelineModel ', () => { + const columns: TimelineModel['columns'] = [ + { + columnHeaderType: 'not-filtered', + id: '@timestamp', + initialWidth: 190, + }, + { + columnHeaderType: 'not-filtered', + id: 'message', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'event.category', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'event.action', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'host.name', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'source.ip', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'destination.ip', + initialWidth: 180, + }, + { + columnHeaderType: 'not-filtered', + id: 'user.name', + initialWidth: 180, + }, + ]; const timelineModel: TimelineModel = { activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.notes, - columns: [ - { - columnHeaderType: 'not-filtered', - id: '@timestamp', - initialWidth: 190, - }, - { - columnHeaderType: 'not-filtered', - id: 'message', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'event.category', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'event.action', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'host.name', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'source.ip', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'destination.ip', - initialWidth: 180, - }, - { - columnHeaderType: 'not-filtered', - id: 'user.name', - initialWidth: 180, - }, - ], + columns, + defaultColumns: columns, dataProviders: [ { id: 'hosts-table-hostName-DESKTOP-QBBSCUT', diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts index 3c2449a2e787dd..7a16b62cd45e6c 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts @@ -80,6 +80,7 @@ export type SubsetTimelineModel = Readonly< | 'activeTab' | 'prevActiveTab' | 'columns' + | 'defaultColumns' | 'dataProviders' | 'deletedEventIds' | 'description' diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts index 8a5c8546d3834b..96ae11cb8afdc2 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts @@ -80,6 +80,7 @@ const basicTimeline: TimelineModel = { activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.graph, columns: [], + defaultColumns: [], dataProviders: [{ ...basicDataProvider }], dateRange: { start: '2020-07-07T08:20:18.966Z', diff --git a/x-pack/plugins/timelines/public/store/t_grid/defaults.ts b/x-pack/plugins/timelines/public/store/t_grid/defaults.ts index 8caae1aabbe017..c211b4709efabe 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/defaults.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/defaults.ts @@ -62,6 +62,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ export const tGridDefaults: SubsetTGridModel = { columns: defaultHeaders, + defaultColumns: defaultHeaders, dateRange: { start: '', end: '' }, deletedEventIds: [], excludedRowRendererIds: [], diff --git a/x-pack/plugins/timelines/public/store/t_grid/model.ts b/x-pack/plugins/timelines/public/store/t_grid/model.ts index 757a1ed66f2fb5..2c39e3739b6916 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/model.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/model.ts @@ -87,6 +87,7 @@ export interface TGridModel extends TGridModelSettings { export type TGridModelForTimeline = Pick< TGridModel, | 'columns' + | 'defaultColumns' | 'dataProviders' | 'dateRange' | 'deletedEventIds' @@ -114,6 +115,7 @@ export type SubsetTGridModel = Readonly< Pick< TGridModel, | 'columns' + | 'defaultColumns' | 'dateRange' | 'deletedEventIds' | 'excludedRowRendererIds' From 336b099eec2e3a0ea99a9cdd6509d2e26fb0d366 Mon Sep 17 00:00:00 2001 From: Michael Marcialis Date: Wed, 18 Aug 2021 11:25:44 -0400 Subject: [PATCH 06/36] Home Page Style Tweaks (#108989) * balance solutions * clean up solution changes * change footer button size * update management section * apply max-width to management items * remove right side items from page header * add data content update * illustration poc * add data content updates per feedback * img size and alignment * moved shared images to shared assets folder * more solutions clean up * rm unneeded import * remove references to subtitle and appDescriptions * update tests and snapshots * more test and snapshot updates * restore solution sort order * ts and jest fixes; thx catherine! * i18n fixes * use new `KibanaPageTemplateSolutionNavAvatar` comp * change solution imgs from png to svg * update tests and snapshots * rm spacer and update snapshots * account for flex margin changes in img offset * Change "Kibana" overview page text to "Analytics" * update overview icon to match hp changes * update snapshots * center justify solutions and update snapshots * update snapshots * title case dev tools and stack management * update text and snapshots * fix merge error * apply caroline's suggested style tweaks * clean up css and update snapshots Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/_manage_data.scss | 2 +- .../application/components/_solutions_section.scss | 14 ++++++++------ .../__snapshots__/solutions_section.test.tsx.snap | 2 -- .../solutions_section/solutions_section.tsx | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/plugins/home/public/application/components/_manage_data.scss b/src/plugins/home/public/application/components/_manage_data.scss index df0219636e17c2..4fcd6122f06122 100644 --- a/src/plugins/home/public/application/components/_manage_data.scss +++ b/src/plugins/home/public/application/components/_manage_data.scss @@ -1,5 +1,5 @@ .homDataManage__item { @include euiBreakpoint('l', 'xl') { - max-width: calc(50% - #{$euiSizeM * 2}); + max-width: calc(33.33% - #{$euiSizeM * 2}); } } \ No newline at end of file diff --git a/src/plugins/home/public/application/components/_solutions_section.scss b/src/plugins/home/public/application/components/_solutions_section.scss index d563b13ac7e072..4bd8351c933b96 100644 --- a/src/plugins/home/public/application/components/_solutions_section.scss +++ b/src/plugins/home/public/application/components/_solutions_section.scss @@ -1,23 +1,25 @@ .homSolutions__item { @include euiBreakpoint('l', 'xl') { - max-width: calc(50% - #{$euiSizeM * 2}); + max-width: calc(33.33% - #{$euiSizeM * 2}); } } -.euiCard__image { - .homSolutionPanel & { +.homSolutionPanel { + img { background-color: $euiColorPrimary; + max-height: $euiSize * 10; + object-fit: cover; } - .homSolutionPanel--enterpriseSearch & { + &--enterpriseSearch img { background-color: $euiColorWarning; } - .homSolutionPanel--observability & { + &--observability img { background-color: $euiColorAccent; } - .homSolutionPanel--securitySolution & { + &--securitySolution img { background-color: $euiColorSecondary; } } \ No newline at end of file diff --git a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap index cef62f4a1e88dc..676745e7f1a524 100644 --- a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap +++ b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap @@ -19,7 +19,6 @@ exports[`SolutionsSection renders a single solution 1`] = ` = ({ addBasePath, solutions }) => { - + {solutions.map((solution) => ( ))} From 28772d7db44ecd9aa2d949d47fd8c22ecaed18a9 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:26:06 -0400 Subject: [PATCH 07/36] [Security Solution] Correct the spelling of behavior in Endpoint policy config (#108969) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pages/policy/view/policy_forms/protections/behavior.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx index a77d661593e6bb..8bfda22fd37015 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx @@ -28,7 +28,7 @@ export const BehaviorProtection = React.memo(() => { const protectionLabel = i18n.translate( 'xpack.securitySolution.endpoint.policy.protections.behavior', { - defaultMessage: 'Behaviour protections', + defaultMessage: 'Behavior protections', } ); return ( From ed78d4b7000b7baa424b0fa4b7d00186a3e7558c Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 18 Aug 2021 18:01:42 +0200 Subject: [PATCH 08/36] [RAC][Observability] temporarily hide severity column (#109004) * [RAC][Observability] temporarily hide severity column * remove severity from flyout & rename Trigerred to last updated Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/pages/alerts/alerts_flyout/index.tsx | 14 ++------------ .../public/pages/alerts/alerts_table_t_grid.tsx | 11 ----------- .../plugins/translations/translations/ja-JP.json | 2 -- .../plugins/translations/translations/zh-CN.json | 2 -- 4 files changed, 2 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx index 90032419948efb..c4d455fb43b7f4 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx @@ -24,7 +24,6 @@ import type { ALERT_DURATION as ALERT_DURATION_TYPED, ALERT_EVALUATION_THRESHOLD as ALERT_EVALUATION_THRESHOLD_TYPED, ALERT_EVALUATION_VALUE as ALERT_EVALUATION_VALUE_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_TYPED, ALERT_UUID as ALERT_UUID_TYPED, ALERT_RULE_CATEGORY as ALERT_RULE_CATEGORY_TYPED, ALERT_RULE_NAME as ALERT_RULE_NAME_TYPED, @@ -33,7 +32,6 @@ import { ALERT_DURATION as ALERT_DURATION_NON_TYPED, ALERT_EVALUATION_THRESHOLD as ALERT_EVALUATION_THRESHOLD_NON_TYPED, ALERT_EVALUATION_VALUE as ALERT_EVALUATION_VALUE_NON_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_NON_TYPED, ALERT_UUID as ALERT_UUID_NON_TYPED, ALERT_RULE_CATEGORY as ALERT_RULE_CATEGORY_NON_TYPED, ALERT_RULE_NAME as ALERT_RULE_NAME_NON_TYPED, @@ -46,7 +44,6 @@ import { useKibana, useUiSetting } from '../../../../../../../src/plugins/kibana import { asDuration } from '../../../../common/utils/formatters'; import type { ObservabilityRuleTypeRegistry } from '../../../rules/create_observability_rule_type_registry'; import { parseAlert } from '../parse_alert'; -import { SeverityBadge } from '../severity_badge'; type AlertsFlyoutProps = { alert?: TopAlert; @@ -59,7 +56,6 @@ type AlertsFlyoutProps = { const ALERT_DURATION: typeof ALERT_DURATION_TYPED = ALERT_DURATION_NON_TYPED; const ALERT_EVALUATION_THRESHOLD: typeof ALERT_EVALUATION_THRESHOLD_TYPED = ALERT_EVALUATION_THRESHOLD_NON_TYPED; const ALERT_EVALUATION_VALUE: typeof ALERT_EVALUATION_VALUE_TYPED = ALERT_EVALUATION_VALUE_NON_TYPED; -const ALERT_SEVERITY_LEVEL: typeof ALERT_SEVERITY_LEVEL_TYPED = ALERT_SEVERITY_LEVEL_NON_TYPED; const ALERT_UUID: typeof ALERT_UUID_TYPED = ALERT_UUID_NON_TYPED; const ALERT_RULE_CATEGORY: typeof ALERT_RULE_CATEGORY_TYPED = ALERT_RULE_CATEGORY_NON_TYPED; const ALERT_RULE_NAME: typeof ALERT_RULE_NAME_TYPED = ALERT_RULE_NAME_NON_TYPED; @@ -97,14 +93,8 @@ export function AlertsFlyout({ description: alertData.active ? 'Active' : 'Recovered', }, { - title: i18n.translate('xpack.observability.alertsFlyout.severityLabel', { - defaultMessage: 'Severity', - }), - description: , - }, - { - title: i18n.translate('xpack.observability.alertsFlyout.triggeredLabel', { - defaultMessage: 'Triggered', + title: i18n.translate('xpack.observability.alertsFlyout.lastUpdatedLabel', { + defaultMessage: 'Last updated', }), description: ( {moment(alertData.start).format(dateFormat)} diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index 135a63b78dc724..bbbd81b4e49eae 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -13,14 +13,12 @@ import { AlertConsumers as AlertConsumersTyped, ALERT_DURATION as ALERT_DURATION_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_TYPED, ALERT_STATUS as ALERT_STATUS_TYPED, ALERT_REASON as ALERT_REASON_TYPED, ALERT_RULE_CONSUMER, } from '@kbn/rule-data-utils'; import { ALERT_DURATION as ALERT_DURATION_NON_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_NON_TYPED, ALERT_STATUS as ALERT_STATUS_NON_TYPED, ALERT_REASON as ALERT_REASON_NON_TYPED, TIMESTAMP, @@ -65,7 +63,6 @@ import { CoreStart } from '../../../../../../src/core/public'; const AlertConsumers: typeof AlertConsumersTyped = AlertConsumersNonTyped; const ALERT_DURATION: typeof ALERT_DURATION_TYPED = ALERT_DURATION_NON_TYPED; -const ALERT_SEVERITY_LEVEL: typeof ALERT_SEVERITY_LEVEL_TYPED = ALERT_SEVERITY_LEVEL_NON_TYPED; const ALERT_STATUS: typeof ALERT_STATUS_TYPED = ALERT_STATUS_NON_TYPED; const ALERT_REASON: typeof ALERT_REASON_TYPED = ALERT_REASON_NON_TYPED; @@ -134,14 +131,6 @@ export const columns: Array< id: ALERT_DURATION, initialWidth: 116, }, - { - columnHeaderType: 'not-filtered', - displayAsText: i18n.translate('xpack.observability.alertsTGrid.severityColumnDescription', { - defaultMessage: 'Severity', - }), - id: ALERT_SEVERITY_LEVEL, - initialWidth: 102, - }, { columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonColumnDescription', { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 68d4482afd92ce..eae33aa422ea1c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18230,9 +18230,7 @@ "xpack.observability.alertsFlyout.durationLabel": "期間", "xpack.observability.alertsFlyout.expectedValueLabel": "想定された値", "xpack.observability.alertsFlyout.ruleTypeLabel": "ルールタイプ", - "xpack.observability.alertsFlyout.severityLabel": "深刻度", "xpack.observability.alertsFlyout.statusLabel": "ステータス", - "xpack.observability.alertsFlyout.triggeredLabel": "実行済み", "xpack.observability.alertsLinkTitle": "アラート", "xpack.observability.alertsTitle": "アラート", "xpack.observability.breadcrumbs.alertsLinkText": "アラート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fa6e840e023b35..08acc43b1b9a55 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18640,9 +18640,7 @@ "xpack.observability.alertsFlyout.durationLabel": "持续时间", "xpack.observability.alertsFlyout.expectedValueLabel": "预期值", "xpack.observability.alertsFlyout.ruleTypeLabel": "规则类型", - "xpack.observability.alertsFlyout.severityLabel": "严重性", "xpack.observability.alertsFlyout.statusLabel": "状态", - "xpack.observability.alertsFlyout.triggeredLabel": "已触发", "xpack.observability.alertsLinkTitle": "告警", "xpack.observability.alertsTitle": "告警", "xpack.observability.breadcrumbs.alertsLinkText": "告警", From 31c7b81a9442b3b6384d19002462169ccad58844 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 18 Aug 2021 19:33:45 +0300 Subject: [PATCH 09/36] [TSVB] Fix Markdown variables are not available on the first rendering (#108836) * [TSVB] Markdown variables are not available on the first rendering Closes: #108721 * Update editor_controller.tsx Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/vis_editor.tsx | 5 +-- .../public/application/editor_controller.tsx | 8 ++-- .../public/metrics_type.ts | 43 ++++++++++++++++--- .../vis_type_timeseries/public/types.ts | 10 +++++ 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx index 4faa0110b97113..152ae43bebd645 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx @@ -8,7 +8,6 @@ import React, { Component } from 'react'; import * as Rx from 'rxjs'; -import uuid from 'uuid/v4'; import { share } from 'rxjs/operators'; import { isEqual, isEmpty, debounce } from 'lodash'; import { EventEmitter } from 'events'; @@ -64,6 +63,7 @@ export class VisEditor extends Component +): Vis => { + const doReplace = ( + obj: Partial<{ + id: string | (() => string); + }> + ) => { + if (typeof obj?.id === 'function') { + obj.id = obj.id(); + } + }; + + doReplace(vis.params); + + vis.params.series?.forEach((series) => { + doReplace(series); + series.metrics?.forEach((metric) => doReplace(metric)); + }); + + return vis; +}; + +export const metricsVisDefinition: VisTypeDefinition< + TimeseriesVisParams | TimeseriesVisDefaultParams +> = { name: 'metrics', title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }), description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', { @@ -26,11 +58,11 @@ export const metricsVisDefinition = { group: VisGroups.PROMOTED, visConfig: { defaults: { - id: uuid(), + id: () => uuid(), type: PANEL_TYPES.TIMESERIES, series: [ { - id: uuid(), + id: () => uuid(), color: TSVB_DEFAULT_COLOR, split_mode: 'everything', palette: { @@ -39,7 +71,7 @@ export const metricsVisDefinition = { }, metrics: [ { - id: uuid(), + id: () => uuid(), type: 'count', }, ], @@ -66,6 +98,7 @@ export const metricsVisDefinition = { drop_last_bucket: 0, }, }, + setup: (vis) => Promise.resolve(withReplacedIds(vis)), editorConfig: { editor: TSVB_EDITOR_NAME, }, diff --git a/src/plugins/vis_type_timeseries/public/types.ts b/src/plugins/vis_type_timeseries/public/types.ts index dcde3195ba984f..a1fb11eeca0b85 100644 --- a/src/plugins/vis_type_timeseries/public/types.ts +++ b/src/plugins/vis_type_timeseries/public/types.ts @@ -19,3 +19,13 @@ export type DragHandleProps = FirstArgumentOf< >['dragHandleProps']; export type TimeseriesVisParams = Panel; + +export type TimeseriesVisDefaultParams = TimeseriesVisParams & { + id: () => string; + series: { + id: () => string; + metrics: { + id: () => string; + }; + }; +}; From d9c1677f4babc6f1e5e1c9f869435e81ac9e1de6 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Aug 2021 17:37:52 +0100 Subject: [PATCH 10/36] chore(NA): adds 7.16 into backportrc (#109128) --- .backportrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.backportrc.json b/.backportrc.json index f9d0f001c35f62..05c3a60e3625a5 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "targetBranchChoices": [ { "name": "master", "checked": true }, { "name": "7.x", "checked": true }, + "7.15", "7.14", "7.13", "7.12", @@ -32,7 +33,7 @@ "targetPRLabels": ["backport"], "branchLabelMapping": { "^v8.0.0$": "master", - "^v7.15.0$": "7.x", + "^v7.16.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, From 7be0bb9c786d45248b28e6b83186307298649069 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Aug 2021 11:01:47 -0600 Subject: [PATCH 11/36] Abort full screen in dashboard and maps when user clicks back button (#108747) * [Maps] reverse geocoding tutorial * reverse geocoding step * add final step * use dash delemiter instead of underscore in file name * add float to step 3 so its on the same page * add into to step 3 * Abort full screen in dashboard and maps when user clicks back button * remove doc changes from another PR * remove change in file heading * tslint and fix unit test * eslint Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/dashboard/public/plugin.tsx | 23 ++----------------- .../exit_full_screen_button.test.tsx.snap | 5 ++++ .../exit_full_screen_button.test.tsx | 15 +++++++++--- .../exit_full_screen_button.tsx | 6 ++++- .../map_container/index.ts | 6 +---- .../map_container/map_container.tsx | 5 +++- .../public/routes/map_page/top_nav_config.tsx | 2 -- 7 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 53a8e90a8c35c9..3da299bb200956 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -183,27 +183,8 @@ export class DashboardPlugin const getStartServices = async () => { const [coreStart, deps] = await core.getStartServices(); - const useHideChrome = ({ toggleChrome } = { toggleChrome: true }) => { - React.useEffect(() => { - if (toggleChrome) { - coreStart.chrome.setIsVisible(false); - } - - return () => { - if (toggleChrome) { - coreStart.chrome.setIsVisible(true); - } - }; - }, [toggleChrome]); - }; - - const ExitFullScreenButton: React.FC< - ExitFullScreenButtonProps & { - toggleChrome: boolean; - } - > = ({ toggleChrome, ...props }) => { - useHideChrome({ toggleChrome }); - return ; + const ExitFullScreenButton: React.FC = (props) => { + return ; }; return { SavedObjectFinder: getSavedObjectFinder(coreStart.savedObjects, coreStart.uiSettings), diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap b/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap index 8541558eb4cea2..0ac76baa5c3e2f 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap +++ b/src/plugins/kibana_react/public/exit_full_screen_button/__snapshots__/exit_full_screen_button.test.tsx.snap @@ -2,6 +2,11 @@ exports[`is rendered 1`] = `
diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx index 750c45f2dfa00e..430eae9f1234f0 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.test.tsx @@ -11,9 +11,16 @@ import sinon from 'sinon'; import { ExitFullScreenButton } from './exit_full_screen_button'; import { keys } from '@elastic/eui'; import { mount } from 'enzyme'; +import type { ChromeStart } from '../../../../core/public'; + +const MockChrome = ({ + setIsVisible: () => {}, +} as unknown) as ChromeStart; test('is rendered', () => { - const component = mount( {}} />); + const component = mount( + {}} chrome={MockChrome} /> + ); expect(component).toMatchSnapshot(); }); @@ -22,7 +29,9 @@ describe('onExitFullScreenMode', () => { test('is called when the button is pressed', () => { const onExitHandler = sinon.stub(); - const component = mount(); + const component = mount( + + ); component.find('button').simulate('click'); @@ -32,7 +41,7 @@ describe('onExitFullScreenMode', () => { test('is called when the ESC key is pressed', () => { const onExitHandler = sinon.stub(); - mount(); + mount(); const escapeKeyEvent = new KeyboardEvent('keydown', { key: keys.ESCAPE } as any); document.dispatchEvent(escapeKeyEvent); diff --git a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx index 3389c32de318f9..9b82757523a837 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -10,9 +10,11 @@ import { i18n } from '@kbn/i18n'; import React, { PureComponent } from 'react'; import { EuiScreenReaderOnly, keys } from '@elastic/eui'; import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import type { ChromeStart } from '../../../../core/public'; export interface ExitFullScreenButtonProps { onExitFullScreenMode: () => void; + chrome: ChromeStart; } import './index.scss'; @@ -24,11 +26,13 @@ class ExitFullScreenButtonUi extends PureComponent { } }; - public UNSAFE_componentWillMount() { + public componentDidMount() { + this.props.chrome.setIsVisible(false); document.addEventListener('keydown', this.onKeyDown, false); } public componentWillUnmount() { + this.props.chrome.setIsVisible(true); document.removeEventListener('keydown', this.onKeyDown, false); } diff --git a/x-pack/plugins/maps/public/connected_components/map_container/index.ts b/x-pack/plugins/maps/public/connected_components/map_container/index.ts index 4831cd60474b97..28671b13df5bc0 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/index.ts +++ b/x-pack/plugins/maps/public/connected_components/map_container/index.ts @@ -19,7 +19,6 @@ import { getQueryableUniqueIndexPatternIds, } from '../../selectors/map_selectors'; import { MapStoreState } from '../../reducers/store'; -import { getCoreChrome } from '../../kibana_services'; function mapStateToProps(state: MapStoreState) { return { @@ -35,10 +34,7 @@ function mapStateToProps(state: MapStoreState) { function mapDispatchToProps(dispatch: ThunkDispatch) { return { - exitFullScreen: () => { - dispatch(exitFullScreen()); - getCoreChrome().setIsVisible(true); - }, + exitFullScreen: () => dispatch(exitFullScreen()), cancelAllInFlightRequests: () => dispatch(cancelAllInFlightRequests()), }; } diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 0bdf462cca4b3a..a8b4b48b87989c 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -21,6 +21,7 @@ import { ToolbarOverlay } from '../toolbar_overlay'; import { EditLayerPanel } from '../edit_layer_panel'; import { AddLayerPanel } from '../add_layer_panel'; import { ExitFullScreenButton } from '../../../../../../src/plugins/kibana_react/public'; +import { getCoreChrome } from '../../kibana_services'; import { RawValue } from '../../../common/constants'; import { FLYOUT_STATE } from '../../reducers/ui'; import { MapSettings } from '../../reducers/map'; @@ -190,7 +191,9 @@ export class MapContainer extends Component { let exitFullScreenButton; if (isFullScreen) { - exitFullScreenButton = ; + exitFullScreenButton = ( + + ); } return ( Date: Wed, 18 Aug 2021 11:06:04 -0600 Subject: [PATCH 12/36] Security Telemetry Usage Telemetry Counters (#108735) * added UsageCounter to SecuritySolution app to be passed to telemetry and other plugins as needed * Add counters for payloads and helpers for naming * Fixed some typing issues * Fixed eslint errors * Still more eslint fixes * Missed an eslint fix again * Incorrect import order * Addressed some review comments * Added unit test for UsageCounter inside TaskSender * Fixed some import checks * incrementCounter unittest needs questionmark to handle undefined case Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/telemetry/helpers.ts | 9 ++++ .../server/lib/telemetry/sender.test.ts | 15 ++++++- .../server/lib/telemetry/sender.ts | 44 ++++++++++++++++++- .../security_solution/server/plugin.ts | 14 +++++- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index b32bd64a073378..6af258a4cbe642 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -83,3 +83,12 @@ export function isPackagePolicyList( return (data as PackagePolicy[])[0].inputs !== undefined; } + +/** + * Convert counter label list to kebab case + * @params label_list the list of labels to create standardized UsageCounter from + * @returns a string label for usage in the UsageCounter + */ +export function createUsageCounterLabel(labelList: string[]): string { + return labelList.join('-'); +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts index e6a53e520ee011..4e6520b67ab054 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts @@ -8,10 +8,15 @@ /* eslint-disable dot-notation */ import { TelemetryEventsSender, copyAllowlistedFields, getV3UrlFromV2 } from './sender'; import { loggingSystemMock } from 'src/core/server/mocks'; +import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock'; import { URL } from 'url'; describe('TelemetryEventsSender', () => { let logger: ReturnType; + const usageCountersServiceSetup = usageCountersServiceMock.createSetupContract(); + const telemetryUsageCounter = usageCountersServiceSetup.createUsageCounter( + 'testTelemetryUsageCounter' + ); beforeEach(() => { logger = loggingSystemMock.createLogger(); @@ -163,19 +168,26 @@ describe('TelemetryEventsSender', () => { it('empties the queue when sending', async () => { const sender = new TelemetryEventsSender(logger); - sender['sendEvents'] = jest.fn(); sender['telemetryStart'] = { getIsOptedIn: jest.fn(async () => true), }; sender['telemetrySetup'] = { getTelemetryUrl: jest.fn(async () => new URL('https://telemetry.elastic.co')), }; + sender['telemetryUsageCounter'] = telemetryUsageCounter; sender['fetchClusterInfo'] = jest.fn(async () => { return { cluster_name: 'test', cluster_uuid: 'test-uuid', }; }); + sender['sendEvents'] = jest.fn(async () => { + sender['telemetryUsageCounter']?.incrementCounter({ + counterName: 'test_counter', + counterType: 'invoked', + incrementBy: 1, + }); + }); sender.queueTelemetryEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); expect(sender['queue'].length).toBe(2); @@ -188,6 +200,7 @@ describe('TelemetryEventsSender', () => { await sender['sendIfDue'](); expect(sender['queue'].length).toBe(0); expect(sender['sendEvents']).toBeCalledTimes(2); + expect(sender['telemetryUsageCounter'].incrementCounter).toBeCalledTimes(2); }); it("shouldn't send when telemetry is disabled", async () => { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 7dbef74da2fc85..5724c61bfcee78 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -12,11 +12,13 @@ import { SearchRequest } from '@elastic/elasticsearch/api/types'; import { URL } from 'url'; import { CoreStart, ElasticsearchClient, Logger } from 'src/core/server'; import { TelemetryPluginStart, TelemetryPluginSetup } from 'src/plugins/telemetry/server'; +import { UsageCounter } from 'src/plugins/usage_collection/server'; import { transformDataToNdjson } from '../../utils/read_stream/create_stream_from_ndjson'; import { TaskManagerSetupContract, TaskManagerStartContract, } from '../../../../task_manager/server'; +import { createUsageCounterLabel } from './helpers'; import { TelemetryDiagTask } from './diagnostic_task'; import { TelemetryEndpointTask } from './endpoint_task'; import { TelemetryTrustedAppsTask } from './trusted_apps_task'; @@ -27,6 +29,7 @@ import { getTrustedAppsList } from '../../endpoint/routes/trusted_apps/service'; type BaseSearchTypes = string | number | boolean | object; export type SearchTypes = BaseSearchTypes | BaseSearchTypes[] | undefined; +const usageLabelPrefix: string[] = ['security_telemetry', 'sender']; export interface TelemetryEvent { [key: string]: SearchTypes; @@ -66,13 +69,19 @@ export class TelemetryEventsSender { private esClient?: ElasticsearchClient; private savedObjectsClient?: SavedObjectsClientContract; private exceptionListClient?: ExceptionListClient; + private telemetryUsageCounter?: UsageCounter; constructor(logger: Logger) { this.logger = logger.get('telemetry_events'); } - public setup(telemetrySetup?: TelemetryPluginSetup, taskManager?: TaskManagerSetupContract) { + public setup( + telemetrySetup?: TelemetryPluginSetup, + taskManager?: TaskManagerSetupContract, + telemetryUsageCounter?: UsageCounter + ) { this.telemetrySetup = telemetrySetup; + this.telemetryUsageCounter = telemetryUsageCounter; if (taskManager) { this.diagTask = new TelemetryDiagTask(this.logger, taskManager, this); @@ -285,6 +294,16 @@ export class TelemetryEventsSender { } if (events.length > this.maxQueueSize - qlength) { + this.telemetryUsageCounter?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelPrefix.concat(['queue_stats'])), + counterType: 'docs_lost', + incrementBy: events.length, + }); + this.telemetryUsageCounter?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelPrefix.concat(['queue_stats'])), + counterType: 'num_capacity_exceeded', + incrementBy: 1, + }); this.queue.push(...this.processEvents(events.slice(0, this.maxQueueSize - qlength))); } else { this.queue.push(...this.processEvents(events)); @@ -344,6 +363,7 @@ export class TelemetryEventsSender { await this.sendEvents( toSend, telemetryUrl, + 'alerts-endpoint', clusterInfo.cluster_uuid, clusterInfo.version?.number, licenseInfo?.uid @@ -379,6 +399,7 @@ export class TelemetryEventsSender { await this.sendEvents( toSend, telemetryUrl, + channel, clusterInfo.cluster_uuid, clusterInfo.version?.number, licenseInfo?.uid @@ -429,6 +450,7 @@ export class TelemetryEventsSender { private async sendEvents( events: unknown[], telemetryUrl: string, + channel: string, clusterUuid: string, clusterVersionNumber: string | undefined, licenseId: string | undefined @@ -445,11 +467,31 @@ export class TelemetryEventsSender { ...(licenseId ? { 'X-Elastic-License-ID': licenseId } : {}), }, }); + this.telemetryUsageCounter?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])), + counterType: resp.status.toString(), + incrementBy: 1, + }); + this.telemetryUsageCounter?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])), + counterType: 'docs_sent', + incrementBy: events.length, + }); this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); } catch (err) { this.logger.warn( `Error sending events: ${err.response.status} ${JSON.stringify(err.response.data)}` ); + this.telemetryUsageCounter?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])), + counterType: 'docs_lost', + incrementBy: events.length, + }); + this.telemetryUsageCounter?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])), + counterType: 'num_exceptions', + incrementBy: 1, + }); } } } diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 9176c7ee70ba1d..e5c837293e2a85 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -21,7 +21,10 @@ import { PluginSetup as DataPluginSetup, PluginStart as DataPluginStart, } from '../../../../src/plugins/data/server'; -import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; +import { + UsageCollectionSetup, + UsageCounter, +} from '../../../../src/plugins/usage_collection/server'; import { PluginSetupContract as AlertingSetup, PluginStartContract as AlertPluginStartContract, @@ -143,6 +146,7 @@ export class Plugin implements IPlugin; + private telemetryUsageCounter?: UsageCounter; constructor(context: PluginInitializerContext) { this.context = context; @@ -181,6 +185,8 @@ export class Plugin implements IPlugin(); core.http.registerRouteHandlerContext( APP_ID, @@ -329,7 +335,11 @@ export class Plugin implements IPlugin Date: Wed, 18 Aug 2021 20:18:05 +0300 Subject: [PATCH 13/36] [Elasticsearch] remove legacy es client (#107619) * remove legacy es client * update docs * uninstall elasticsearch package * fix global_search tests * ad-hoc fix to address bazel failure. authored by Tiago * update docs * remove elasticsearch import. errors are muted with @ts-ignore * Update WORKSPACE.bazel Co-authored-by: Tiago Costa * update docs * fix problem when dev mock already mocked client Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Tiago Costa --- ...re-server.assistanceapiresponse.indices.md | 15 - ...lugin-core-server.assistanceapiresponse.md | 23 - ...in-core-server.assistantapiclientparams.md | 24 - ...-server.assistantapiclientparams.method.md | 11 - ...re-server.assistantapiclientparams.path.md | 11 - ...-core-server.deprecationapiclientparams.md | 24 - ...erver.deprecationapiclientparams.method.md | 11 - ...-server.deprecationapiclientparams.path.md | 11 - ...deprecationapiresponse.cluster_settings.md | 11 - ...r.deprecationapiresponse.index_settings.md | 11 - ...ugin-core-server.deprecationapiresponse.md | 26 - ...rver.deprecationapiresponse.ml_settings.md | 11 - ...er.deprecationapiresponse.node_settings.md | 11 - ...gin-core-server.deprecationinfo.details.md | 11 - ...lugin-core-server.deprecationinfo.level.md | 11 - ...bana-plugin-core-server.deprecationinfo.md | 26 - ...gin-core-server.deprecationinfo.message.md | 11 - ...-plugin-core-server.deprecationinfo.url.md | 11 - ...server.elasticsearchservicesetup.legacy.md | 2 - ...n-core-server.elasticsearchservicesetup.md | 2 +- ...server.elasticsearchservicestart.legacy.md | 2 - ...n-core-server.elasticsearchservicestart.md | 2 +- ...plugin-core-server.ilegacyclusterclient.md | 20 - ...-core-server.ilegacycustomclusterclient.md | 20 - ...-core-server.ilegacyscopedclusterclient.md | 20 - ...ore-server.indexsettingsdeprecationinfo.md | 16 - ...bana-plugin-core-server.legacyapicaller.md | 16 - ...plugin-core-server.legacycallapioptions.md | 26 - ...core-server.legacycallapioptions.signal.md | 13 - ...rver.legacycallapioptions.wrap401errors.md | 13 - ...erver.legacyclusterclient._constructor_.md | 23 - ...ore-server.legacyclusterclient.asscoped.md | 24 - ....legacyclusterclient.callasinternaluser.md | 18 - ...n-core-server.legacyclusterclient.close.md | 17 - ...-plugin-core-server.legacyclusterclient.md | 38 - ...-server.legacyelasticsearchclientconfig.md | 20 - ...-server.legacyelasticsearcherror._code_.md | 11 - ...in-core-server.legacyelasticsearcherror.md | 20 - ...errorhelpers.decoratenotauthorizederror.md | 23 - ...searcherrorhelpers.isnotauthorizederror.md | 22 - ...-server.legacyelasticsearcherrorhelpers.md | 35 - ...legacyscopedclusterclient._constructor_.md | 22 - ...cyscopedclusterclient.callascurrentuser.md | 31 - ...yscopedclusterclient.callasinternaluser.md | 31 - ...n-core-server.legacyscopedclusterclient.md | 32 - .../core/server/kibana-plugin-core-server.md | 20 +- ...erver.migration_assistance_index_action.md | 16 - ...core-server.migration_deprecation_level.md | 16 - ...-core-server.requesthandlercontext.core.md | 3 - ...lugin-core-server.requesthandlercontext.md | 4 +- ...plugin-plugins-data-server.plugin.start.md | 4 +- package.json | 5 +- .../server/core_route_handler_context.test.ts | 37 - src/core/server/core_route_handler_context.ts | 18 +- src/core/server/elasticsearch/client/mocks.ts | 9 +- .../elasticsearch_service.mock.ts | 30 +- .../elasticsearch_service.test.mocks.ts | 3 - .../elasticsearch_service.test.ts | 147 +--- .../elasticsearch/elasticsearch_service.ts | 36 +- src/core/server/elasticsearch/index.ts | 1 - .../server/elasticsearch/legacy/api_types.ts | 392 --------- .../legacy/cluster_client.test.mocks.ts | 27 - .../legacy/cluster_client.test.ts | 547 ------------- .../elasticsearch/legacy/cluster_client.ts | 256 ------ .../elasticsearch_client_config.test.ts | 756 ------------------ .../legacy/elasticsearch_client_config.ts | 224 ------ .../elasticsearch/legacy/errors.test.ts | 63 -- .../server/elasticsearch/legacy/errors.ts | 84 -- src/core/server/elasticsearch/legacy/index.ts | 18 - src/core/server/elasticsearch/legacy/mocks.ts | 86 -- .../legacy/scoped_cluster_client.test.ts | 198 ----- .../legacy/scoped_cluster_client.ts | 98 --- src/core/server/elasticsearch/types.ts | 82 -- .../core_service.test.mocks.ts | 21 - .../integration_tests/core_services.test.ts | 103 +-- src/core/server/http/router/router.ts | 5 - src/core/server/index.ts | 27 - src/core/server/mocks.ts | 3 - src/core/server/server.api.md | 488 +---------- src/plugins/data/server/server.api.md | 2 +- .../plugins/core_plugin_a/server/plugin.ts | 6 +- .../server/services/context.mock.ts | 6 - .../server/services/context.test.ts | 4 - .../global_search/server/services/context.ts | 5 - x-pack/plugins/global_search/server/types.ts | 6 - .../routes/__mocks__/request_context.ts | 2 - .../tests/geo_containment.test.ts | 5 +- .../task_manager_fixture/server/plugin.ts | 2 +- .../sample_task_plugin/server/init_routes.ts | 13 +- .../sample_task_plugin/server/plugin.ts | 5 +- yarn.lock | 2 +- 91 files changed, 43 insertions(+), 4631 deletions(-) delete mode 100644 docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.indices.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.method.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.path.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.method.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.path.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.cluster_settings.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.index_settings.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.ml_settings.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.node_settings.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationinfo.details.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationinfo.level.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationinfo.message.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationinfo.url.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.signal.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.wrap401errors.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyclusterclient._constructor_.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.asscoped.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.close.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror._code_.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md delete mode 100644 docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md delete mode 100644 src/core/server/elasticsearch/legacy/api_types.ts delete mode 100644 src/core/server/elasticsearch/legacy/cluster_client.test.mocks.ts delete mode 100644 src/core/server/elasticsearch/legacy/cluster_client.test.ts delete mode 100644 src/core/server/elasticsearch/legacy/cluster_client.ts delete mode 100644 src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts delete mode 100644 src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts delete mode 100644 src/core/server/elasticsearch/legacy/errors.test.ts delete mode 100644 src/core/server/elasticsearch/legacy/errors.ts delete mode 100644 src/core/server/elasticsearch/legacy/index.ts delete mode 100644 src/core/server/elasticsearch/legacy/mocks.ts delete mode 100644 src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts delete mode 100644 src/core/server/elasticsearch/legacy/scoped_cluster_client.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.indices.md b/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.indices.md deleted file mode 100644 index e21c213b1c8265..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.indices.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AssistanceAPIResponse](./kibana-plugin-core-server.assistanceapiresponse.md) > [indices](./kibana-plugin-core-server.assistanceapiresponse.indices.md) - -## AssistanceAPIResponse.indices property - -Signature: - -```typescript -indices: { - [indexName: string]: { - action_required: MIGRATION_ASSISTANCE_INDEX_ACTION; - }; - }; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md b/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md deleted file mode 100644 index 1daaf95a73d5dd..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.assistanceapiresponse.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AssistanceAPIResponse](./kibana-plugin-core-server.assistanceapiresponse.md) - -## AssistanceAPIResponse interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export interface AssistanceAPIResponse -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [indices](./kibana-plugin-core-server.assistanceapiresponse.indices.md) | {
[indexName: string]: {
action_required: MIGRATION_ASSISTANCE_INDEX_ACTION;
};
} | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md b/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md deleted file mode 100644 index 1031d733fed4ab..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AssistantAPIClientParams](./kibana-plugin-core-server.assistantapiclientparams.md) - -## AssistantAPIClientParams interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export interface AssistantAPIClientParams extends GenericParams -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [method](./kibana-plugin-core-server.assistantapiclientparams.method.md) | 'GET' | | -| [path](./kibana-plugin-core-server.assistantapiclientparams.path.md) | '/_migration/assistance' | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.method.md b/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.method.md deleted file mode 100644 index 1d93206fe5e147..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AssistantAPIClientParams](./kibana-plugin-core-server.assistantapiclientparams.md) > [method](./kibana-plugin-core-server.assistantapiclientparams.method.md) - -## AssistantAPIClientParams.method property - -Signature: - -```typescript -method: 'GET'; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.path.md b/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.path.md deleted file mode 100644 index 1386733d0d8a82..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.assistantapiclientparams.path.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AssistantAPIClientParams](./kibana-plugin-core-server.assistantapiclientparams.md) > [path](./kibana-plugin-core-server.assistantapiclientparams.path.md) - -## AssistantAPIClientParams.path property - -Signature: - -```typescript -path: '/_migration/assistance'; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md deleted file mode 100644 index fc1748d4db9079..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) - -## DeprecationAPIClientParams interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export interface DeprecationAPIClientParams extends GenericParams -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [method](./kibana-plugin-core-server.deprecationapiclientparams.method.md) | 'GET' | | -| [path](./kibana-plugin-core-server.deprecationapiclientparams.path.md) | '/_migration/deprecations' | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.method.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.method.md deleted file mode 100644 index 71724c2467b87b..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) > [method](./kibana-plugin-core-server.deprecationapiclientparams.method.md) - -## DeprecationAPIClientParams.method property - -Signature: - -```typescript -method: 'GET'; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.path.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.path.md deleted file mode 100644 index 3fe7b0e8e92378..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiclientparams.path.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) > [path](./kibana-plugin-core-server.deprecationapiclientparams.path.md) - -## DeprecationAPIClientParams.path property - -Signature: - -```typescript -path: '/_migration/deprecations'; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.cluster_settings.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.cluster_settings.md deleted file mode 100644 index ef612d0901682e..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.cluster_settings.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) > [cluster\_settings](./kibana-plugin-core-server.deprecationapiresponse.cluster_settings.md) - -## DeprecationAPIResponse.cluster\_settings property - -Signature: - -```typescript -cluster_settings: DeprecationInfo[]; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.index_settings.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.index_settings.md deleted file mode 100644 index 50b2af591c5a74..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.index_settings.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) > [index\_settings](./kibana-plugin-core-server.deprecationapiresponse.index_settings.md) - -## DeprecationAPIResponse.index\_settings property - -Signature: - -```typescript -index_settings: IndexSettingsDeprecationInfo; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md deleted file mode 100644 index ce40bd7c750f01..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) - -## DeprecationAPIResponse interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export interface DeprecationAPIResponse -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [cluster\_settings](./kibana-plugin-core-server.deprecationapiresponse.cluster_settings.md) | DeprecationInfo[] | | -| [index\_settings](./kibana-plugin-core-server.deprecationapiresponse.index_settings.md) | IndexSettingsDeprecationInfo | | -| [ml\_settings](./kibana-plugin-core-server.deprecationapiresponse.ml_settings.md) | DeprecationInfo[] | | -| [node\_settings](./kibana-plugin-core-server.deprecationapiresponse.node_settings.md) | DeprecationInfo[] | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.ml_settings.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.ml_settings.md deleted file mode 100644 index 641847fd1159d5..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.ml_settings.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) > [ml\_settings](./kibana-plugin-core-server.deprecationapiresponse.ml_settings.md) - -## DeprecationAPIResponse.ml\_settings property - -Signature: - -```typescript -ml_settings: DeprecationInfo[]; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.node_settings.md b/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.node_settings.md deleted file mode 100644 index 9473fd2c1d1ad4..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationapiresponse.node_settings.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) > [node\_settings](./kibana-plugin-core-server.deprecationapiresponse.node_settings.md) - -## DeprecationAPIResponse.node\_settings property - -Signature: - -```typescript -node_settings: DeprecationInfo[]; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.details.md b/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.details.md deleted file mode 100644 index f33f2878a9d019..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.details.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) > [details](./kibana-plugin-core-server.deprecationinfo.details.md) - -## DeprecationInfo.details property - -Signature: - -```typescript -details?: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.level.md b/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.level.md deleted file mode 100644 index 2543c19e141e12..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.level.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) > [level](./kibana-plugin-core-server.deprecationinfo.level.md) - -## DeprecationInfo.level property - -Signature: - -```typescript -level: MIGRATION_DEPRECATION_LEVEL; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md b/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md deleted file mode 100644 index d9d1c6c3edb41e..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) - -## DeprecationInfo interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export interface DeprecationInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [details](./kibana-plugin-core-server.deprecationinfo.details.md) | string | | -| [level](./kibana-plugin-core-server.deprecationinfo.level.md) | MIGRATION_DEPRECATION_LEVEL | | -| [message](./kibana-plugin-core-server.deprecationinfo.message.md) | string | | -| [url](./kibana-plugin-core-server.deprecationinfo.url.md) | string | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.message.md b/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.message.md deleted file mode 100644 index 40bcc4f3a5b3d5..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.message.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) > [message](./kibana-plugin-core-server.deprecationinfo.message.md) - -## DeprecationInfo.message property - -Signature: - -```typescript -message: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.url.md b/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.url.md deleted file mode 100644 index 893d0bc10886ca..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationinfo.url.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) > [url](./kibana-plugin-core-server.deprecationinfo.url.md) - -## DeprecationInfo.url property - -Signature: - -```typescript -url: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md index abcbbf18a8f9ce..bcc2f474fa483c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md @@ -14,7 +14,5 @@ ```typescript legacy: { readonly config$: Observable; - readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; - readonly client: ILegacyClusterClient; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md index ca6134cd5ed651..e6a4161674f5bc 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md @@ -15,5 +15,5 @@ export interface ElasticsearchServiceSetup | Property | Type | Description | | --- | --- | --- | -| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
} | | +| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md index 4026483894aa14..844ebf3815a99f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md @@ -14,7 +14,5 @@ ```typescript legacy: { readonly config$: Observable; - readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; - readonly client: ILegacyClusterClient; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md index 8d9cd1be148cfe..50216edb48f419 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md @@ -17,5 +17,5 @@ export interface ElasticsearchServiceStart | --- | --- | --- | | [client](./kibana-plugin-core-server.elasticsearchservicestart.client.md) | IClusterClient | A pre-configured [Elasticsearch client](./kibana-plugin-core-server.iclusterclient.md) | | [createClient](./kibana-plugin-core-server.elasticsearchservicestart.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient | Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). | -| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
} | | +| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md deleted file mode 100644 index d1e87feba0f033..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ILegacyClusterClient](./kibana-plugin-core-server.ilegacyclusterclient.md) - -## ILegacyClusterClient type - -> Warning: This API is now obsolete. -> -> Use [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). 7.16 -> - -Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). - -See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). - -Signature: - -```typescript -export declare type ILegacyClusterClient = Pick; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md deleted file mode 100644 index c004ad2548802f..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ILegacyCustomClusterClient](./kibana-plugin-core-server.ilegacycustomclusterclient.md) - -## ILegacyCustomClusterClient type - -> Warning: This API is now obsolete. -> -> Use [ICustomClusterClient](./kibana-plugin-core-server.icustomclusterclient.md). 7.16 -> - -Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). - -See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). - -Signature: - -```typescript -export declare type ILegacyCustomClusterClient = Pick; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md deleted file mode 100644 index 8e7ecdb9f7ec2e..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) - -## ILegacyScopedClusterClient type - -> Warning: This API is now obsolete. -> -> Use [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md). 7.16 -> - -Serves the same purpose as "normal" `ClusterClient` but exposes additional `callAsCurrentUser` method that doesn't use credentials of the Kibana internal user (as `callAsInternalUser` does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API. - -See [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md). - -Signature: - -```typescript -export declare type ILegacyScopedClusterClient = Pick; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md b/docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md deleted file mode 100644 index 9103f9cfc67405..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.indexsettingsdeprecationinfo.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IndexSettingsDeprecationInfo](./kibana-plugin-core-server.indexsettingsdeprecationinfo.md) - -## IndexSettingsDeprecationInfo interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export interface IndexSettingsDeprecationInfo -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md b/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md deleted file mode 100644 index 2378e61484da5a..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md) - -## LegacyAPICaller interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export interface LegacyAPICaller -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md deleted file mode 100644 index 219180af26fd83..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) - -## LegacyCallAPIOptions interface - -> Warning: This API is now obsolete. -> -> 7.16 -> - -The set of options that defines how API call should be made and result be processed. - -Signature: - -```typescript -export interface LegacyCallAPIOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [signal](./kibana-plugin-core-server.legacycallapioptions.signal.md) | AbortSignal | A signal object that allows you to abort the request via an AbortController object. | -| [wrap401Errors](./kibana-plugin-core-server.legacycallapioptions.wrap401errors.md) | boolean | Indicates whether 401 Unauthorized errors returned from the Elasticsearch API should be wrapped into Boom error instances with properly set WWW-Authenticate header that could have been returned by the API itself. If API didn't specify that then Basic realm="Authorization Required" is used as WWW-Authenticate. | - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.signal.md b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.signal.md deleted file mode 100644 index 7d795a59e41a54..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.signal.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) > [signal](./kibana-plugin-core-server.legacycallapioptions.signal.md) - -## LegacyCallAPIOptions.signal property - -A signal object that allows you to abort the request via an AbortController object. - -Signature: - -```typescript -signal?: AbortSignal; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.wrap401errors.md b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.wrap401errors.md deleted file mode 100644 index 38fac54db77a42..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.wrap401errors.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) > [wrap401Errors](./kibana-plugin-core-server.legacycallapioptions.wrap401errors.md) - -## LegacyCallAPIOptions.wrap401Errors property - -Indicates whether `401 Unauthorized` errors returned from the Elasticsearch API should be wrapped into `Boom` error instances with properly set `WWW-Authenticate` header that could have been returned by the API itself. If API didn't specify that then `Basic realm="Authorization Required"` is used as `WWW-Authenticate`. - -Signature: - -```typescript -wrap401Errors?: boolean; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient._constructor_.md deleted file mode 100644 index ed2763d9802797..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient._constructor_.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [(constructor)](./kibana-plugin-core-server.legacyclusterclient._constructor_.md) - -## LegacyClusterClient.(constructor) - -Constructs a new instance of the `LegacyClusterClient` class - -Signature: - -```typescript -constructor(config: LegacyElasticsearchClientConfig, log: Logger, type: string, getAuthHeaders?: GetAuthHeaders); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| config | LegacyElasticsearchClientConfig | | -| log | Logger | | -| type | string | | -| getAuthHeaders | GetAuthHeaders | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.asscoped.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.asscoped.md deleted file mode 100644 index 1c25fc1d072b61..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.asscoped.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [asScoped](./kibana-plugin-core-server.legacyclusterclient.asscoped.md) - -## LegacyClusterClient.asScoped() method - -Creates an instance of [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) based on the configuration the current cluster client that exposes additional `callAsCurrentUser` method scoped to the provided req. Consumers shouldn't worry about closing scoped client instances, these will be automatically closed as soon as the original cluster client isn't needed anymore and closed. - -Signature: - -```typescript -asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| request | ScopeableRequest | Request the IScopedClusterClient instance will be scoped to. Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform | - -Returns: - -`ILegacyScopedClusterClient` - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md deleted file mode 100644 index 7c8cc18d24e299..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [callAsInternalUser](./kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md) - -## LegacyClusterClient.callAsInternalUser property - -> Warning: This API is now obsolete. -> -> Use [IClusterClient.asInternalUser](./kibana-plugin-core-server.iclusterclient.asinternaluser.md). -> - -Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). - -Signature: - -```typescript -callAsInternalUser: LegacyAPICaller; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.close.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.close.md deleted file mode 100644 index 88a5ffce5bb17c..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.close.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [close](./kibana-plugin-core-server.legacyclusterclient.close.md) - -## LegacyClusterClient.close() method - -Closes the cluster client. After that client cannot be used and one should create a new client instance to be able to interact with Elasticsearch API. - -Signature: - -```typescript -close(): void; -``` -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md deleted file mode 100644 index 05855c31477c3e..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md +++ /dev/null @@ -1,38 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) - -## LegacyClusterClient class - -> Warning: This API is now obsolete. -> -> Use [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). 7.16 -> - -Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). - -Signature: - -```typescript -export declare class LegacyClusterClient implements ILegacyClusterClient -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(config, log, type, getAuthHeaders)](./kibana-plugin-core-server.legacyclusterclient._constructor_.md) | | Constructs a new instance of the LegacyClusterClient class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [callAsInternalUser](./kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md) | | LegacyAPICaller | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [asScoped(request)](./kibana-plugin-core-server.legacyclusterclient.asscoped.md) | | Creates an instance of [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) based on the configuration the current cluster client that exposes additional callAsCurrentUser method scoped to the provided req. Consumers shouldn't worry about closing scoped client instances, these will be automatically closed as soon as the original cluster client isn't needed anymore and closed. | -| [close()](./kibana-plugin-core-server.legacyclusterclient.close.md) | | Closes the cluster client. After that client cannot be used and one should create a new client instance to be able to interact with Elasticsearch API. | - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md deleted file mode 100644 index a80ebe2fee4937..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchClientConfig](./kibana-plugin-core-server.legacyelasticsearchclientconfig.md) - -## LegacyElasticsearchClientConfig type - -> Warning: This API is now obsolete. -> -> - -Signature: - -```typescript -export declare type LegacyElasticsearchClientConfig = Pick & Pick & { - pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; - requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; - sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; - ssl?: Partial; -}; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror._code_.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror._code_.md deleted file mode 100644 index 05530ceb0d568b..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror._code_.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) > [\[code\]](./kibana-plugin-core-server.legacyelasticsearcherror._code_.md) - -## LegacyElasticsearchError.\[code\] property - -Signature: - -```typescript -[code]?: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md deleted file mode 100644 index 7cf696ad8d73f2..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) - -## LegacyElasticsearchError interface - -@deprecated. The new elasticsearch client doesn't wrap errors anymore. 7.16 - -Signature: - -```typescript -export interface LegacyElasticsearchError extends Boom.Boom -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [\[code\]](./kibana-plugin-core-server.legacyelasticsearcherror._code_.md) | string | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md deleted file mode 100644 index bd802a39e9339f..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) > [decorateNotAuthorizedError](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md) - -## LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError() method - -Signature: - -```typescript -static decorateNotAuthorizedError(error: Error, reason?: string): LegacyElasticsearchError; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | Error | | -| reason | string | | - -Returns: - -`LegacyElasticsearchError` - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md deleted file mode 100644 index f647916149458e..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) > [isNotAuthorizedError](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md) - -## LegacyElasticsearchErrorHelpers.isNotAuthorizedError() method - -Signature: - -```typescript -static isNotAuthorizedError(error: any): error is LegacyElasticsearchError; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | any | | - -Returns: - -`error is LegacyElasticsearchError` - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md deleted file mode 100644 index e20dcd4ed253eb..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) - -## LegacyElasticsearchErrorHelpers class - -Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as `body.error.header[WWW-Authenticate]` - -Signature: - -```typescript -export declare class LegacyElasticsearchErrorHelpers -``` - -## Example - -Handle errors - -```js -try { - await client.asScoped(request).callAsCurrentUser(...); -} catch (err) { - if (ElasticsearchErrorHelpers.isNotAuthorizedError(err)) { - const authHeader = err.output.headers['WWW-Authenticate']; - } - -``` - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [decorateNotAuthorizedError(error, reason)](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md) | static | | -| [isNotAuthorizedError(error)](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md) | static | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md deleted file mode 100644 index bd1cd1e9f3d9b3..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) > [(constructor)](./kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md) - -## LegacyScopedClusterClient.(constructor) - -Constructs a new instance of the `LegacyScopedClusterClient` class - -Signature: - -```typescript -constructor(internalAPICaller: LegacyAPICaller, scopedAPICaller: LegacyAPICaller, headers?: Headers | undefined); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| internalAPICaller | LegacyAPICaller | | -| scopedAPICaller | LegacyAPICaller | | -| headers | Headers | undefined | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md deleted file mode 100644 index 0f2d653e41a554..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md +++ /dev/null @@ -1,31 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) > [callAsCurrentUser](./kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md) - -## LegacyScopedClusterClient.callAsCurrentUser() method - -> Warning: This API is now obsolete. -> -> Use [IScopedClusterClient.asCurrentUser](./kibana-plugin-core-server.iscopedclusterclient.ascurrentuser.md). 7.16 -> - -Calls specified `endpoint` with provided `clientParams` on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). - -Signature: - -```typescript -callAsCurrentUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| endpoint | string | String descriptor of the endpoint e.g. cluster.getSettings or ping. | -| clientParams | Record<string, any> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | -| options | LegacyCallAPIOptions | Options that affect the way we call the API and process the result. | - -Returns: - -`Promise` - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md deleted file mode 100644 index 2c184b0fde5b32..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md +++ /dev/null @@ -1,31 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) > [callAsInternalUser](./kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md) - -## LegacyScopedClusterClient.callAsInternalUser() method - -> Warning: This API is now obsolete. -> -> Use [IScopedClusterClient.asInternalUser](./kibana-plugin-core-server.iscopedclusterclient.asinternaluser.md). 7.16 -> - -Calls specified `endpoint` with provided `clientParams` on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). - -Signature: - -```typescript -callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| endpoint | string | String descriptor of the endpoint e.g. cluster.getSettings or ping. | -| clientParams | Record<string, any> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | -| options | LegacyCallAPIOptions | Options that affect the way we call the API and process the result. | - -Returns: - -`Promise` - diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md deleted file mode 100644 index 6678c3bc16d531..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md +++ /dev/null @@ -1,32 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) - -## LegacyScopedClusterClient class - -> Warning: This API is now obsolete. -> -> Use [scoped cluster client](./kibana-plugin-core-server.iscopedclusterclient.md). 7.16 -> - -Serves the same purpose as the normal [cluster client](./kibana-plugin-core-server.iclusterclient.md) but exposes an additional `asCurrentUser` method that doesn't use credentials of the Kibana internal user (as `asInternalUser` does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API instead. - -Signature: - -```typescript -export declare class LegacyScopedClusterClient implements ILegacyScopedClusterClient -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(internalAPICaller, scopedAPICaller, headers)](./kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md) | | Constructs a new instance of the LegacyScopedClusterClient class | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [callAsCurrentUser(endpoint, clientParams, options)](./kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md) | | Calls specified endpoint with provided clientParams on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). | -| [callAsInternalUser(endpoint, clientParams, options)](./kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md) | | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). | - diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 726432ae134dc5..ba4f5283525669 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -20,9 +20,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [CspConfig](./kibana-plugin-core-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchConfig](./kibana-plugin-core-server.elasticsearchconfig.md) | Wrapper of config schema. | | [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | -| [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)). | -| [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | -| [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) | Serves the same purpose as the normal [cluster client](./kibana-plugin-core-server.iclusterclient.md) but exposes an additional asCurrentUser method that doesn't use credentials of the Kibana internal user (as asInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API instead. | | [RouteValidationError](./kibana-plugin-core-server.routevalidationerror.md) | Error to return when the validation is not successful. | | [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) | | | [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) | | @@ -48,8 +45,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | Interface | Description | | --- | --- | | [AppCategory](./kibana-plugin-core-server.appcategory.md) | A category definition for nav links to know where to sort them in the left hand nav | -| [AssistanceAPIResponse](./kibana-plugin-core-server.assistanceapiresponse.md) | | -| [AssistantAPIClientParams](./kibana-plugin-core-server.assistantapiclientparams.md) | | | [AsyncPlugin](./kibana-plugin-core-server.asyncplugin.md) | A plugin with asynchronous lifecycle methods. | | [Authenticated](./kibana-plugin-core-server.authenticated.md) | | | [AuthNotHandled](./kibana-plugin-core-server.authnothandled.md) | | @@ -68,9 +63,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [CountResponse](./kibana-plugin-core-server.countresponse.md) | | | [CustomHttpResponseOptions](./kibana-plugin-core-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | | [DeleteDocumentResponse](./kibana-plugin-core-server.deletedocumentresponse.md) | | -| [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) | | -| [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) | | -| [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) | | | [DeprecationsClient](./kibana-plugin-core-server.deprecationsclient.md) | Server-side client that provides access to fetch all Kibana deprecations | | [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) | | | [DeprecationSettings](./kibana-plugin-core-server.deprecationsettings.md) | UiSettings deprecation field options. | @@ -105,7 +97,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IExternalUrlPolicy](./kibana-plugin-core-server.iexternalurlpolicy.md) | A policy describing whether access to an external destination is allowed. | | [IKibanaResponse](./kibana-plugin-core-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution | | [IKibanaSocket](./kibana-plugin-core-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | -| [IndexSettingsDeprecationInfo](./kibana-plugin-core-server.indexsettingsdeprecationinfo.md) | | | [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) | | | [IRouter](./kibana-plugin-core-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-core-server.routeconfig.md) and [RequestHandler](./kibana-plugin-core-server.requesthandler.md) for more information about arguments to route registrations. | | [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) | | @@ -113,9 +104,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | | [KibanaRequestEvents](./kibana-plugin-core-server.kibanarequestevents.md) | Request events. | | [KibanaRequestRoute](./kibana-plugin-core-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | -| [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md) | | -| [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) | The set of options that defines how API call should be made and result be processed. | -| [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) | @deprecated. The new elasticsearch client doesn't wrap errors anymore. 7.16 | | [LegacyRequest](./kibana-plugin-core-server.legacyrequest.md) | | | [LoggerContextConfigInput](./kibana-plugin-core-server.loggercontextconfiginput.md) | | | [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) | Provides APIs to plugins for customizing the plugin's logger. | @@ -139,7 +127,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PrebootPlugin](./kibana-plugin-core-server.prebootplugin.md) | The interface that should be returned by a PluginInitializer for a preboot plugin. | | [PrebootServicePreboot](./kibana-plugin-core-server.prebootservicepreboot.md) | Kibana Preboot Service allows to control the boot flow of Kibana. Preboot plugins can use it to hold the boot until certain condition is met. | | [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) | | -| [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.client](./kibana-plugin-core-server.iscopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.legacy.client](./kibana-plugin-core-server.legacyscopedclusterclient.md) - The legacy Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | +| [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.client](./kibana-plugin-core-server.iscopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - - The legacy Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | | [ResolveCapabilitiesOptions](./kibana-plugin-core-server.resolvecapabilitiesoptions.md) | Defines a set of additional options for the resolveCapabilities method of [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md). | | [RouteConfig](./kibana-plugin-core-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-core-server.routeconfigoptions.md) | Additional route options. | @@ -270,9 +258,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpResponsePayload](./kibana-plugin-core-server.httpresponsepayload.md) | Data send to the client as a response payload. | | [IBasePath](./kibana-plugin-core-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-core-server.basepath.md) | | [IContextProvider](./kibana-plugin-core-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | -| [ILegacyClusterClient](./kibana-plugin-core-server.ilegacyclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). | -| [ILegacyCustomClusterClient](./kibana-plugin-core-server.ilegacycustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). | -| [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md). | | [IsAuthenticated](./kibana-plugin-core-server.isauthenticated.md) | Returns authentication status for a request. | | [ISavedObjectsExporter](./kibana-plugin-core-server.isavedobjectsexporter.md) | | | [ISavedObjectsImporter](./kibana-plugin-core-server.isavedobjectsimporter.md) | | @@ -282,13 +267,10 @@ The plugin integrates with the core system via lifecycle events: `setup` | [KibanaRequestRouteOptions](./kibana-plugin-core-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | | [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | | [KnownHeaders](./kibana-plugin-core-server.knownheaders.md) | Set of well-known HTTP headers. | -| [LegacyElasticsearchClientConfig](./kibana-plugin-core-server.legacyelasticsearchclientconfig.md) | | | [LifecycleResponseFactory](./kibana-plugin-core-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | | [LoggerConfigType](./kibana-plugin-core-server.loggerconfigtype.md) | | | [MakeUsageFromSchema](./kibana-plugin-core-server.makeusagefromschema.md) | List of configuration values that will be exposed to usage collection. If parent node or actual config path is set to true then the actual value of these configs will be reoprted. If parent node or actual config path is set to false then the config will be reported as \[redacted\]. | | [MetricsServiceStart](./kibana-plugin-core-server.metricsservicestart.md) | APIs to retrieves metrics gathered and exposed by the core platform. | -| [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-core-server.migration_assistance_index_action.md) | | -| [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-core-server.migration_deprecation_level.md) | | | [MutatingOperationRefreshSetting](./kibana-plugin-core-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | | [OnPostAuthHandler](./kibana-plugin-core-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-core-server.onpostauthtoolkit.md). | | [OnPreAuthHandler](./kibana-plugin-core-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md). | diff --git a/docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md b/docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md deleted file mode 100644 index ea0a277931eaf1..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.migration_assistance_index_action.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-core-server.migration_assistance_index_action.md) - -## MIGRATION\_ASSISTANCE\_INDEX\_ACTION type - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export declare type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md b/docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md deleted file mode 100644 index f71e6e78a4c34b..00000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.migration_deprecation_level.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-core-server.migration_deprecation_level.md) - -## MIGRATION\_DEPRECATION\_LEVEL type - -> Warning: This API is now obsolete. -> -> 7.16 -> - -Signature: - -```typescript -export declare type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md index e54e8f105a2bd7..dcf6975c5fa702 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md @@ -17,9 +17,6 @@ core: { }; elasticsearch: { client: IScopedClusterClient; - legacy: { - client: ILegacyScopedClusterClient; - }; }; uiSettings: { client: IUiSettingsClient; diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md index 59069ec9954937..b6d78f8890b37a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md @@ -6,7 +6,7 @@ Plugin specific context passed to a route handler. -Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.client](./kibana-plugin-core-server.iscopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.legacy.client](./kibana-plugin-core-server.legacyscopedclusterclient.md) - The legacy Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request +Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.client](./kibana-plugin-core-server.iscopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - - The legacy Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request Signature: @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
getClient: (options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
getExporter: (client: SavedObjectsClientContract) => ISavedObjectsExporter;
getImporter: (client: SavedObjectsClientContract) => ISavedObjectsImporter;
};
elasticsearch: {
client: IScopedClusterClient;
legacy: {
client: ILegacyScopedClusterClient;
};
};
uiSettings: {
client: IUiSettingsClient;
};
deprecations: {
client: DeprecationsClient;
};
} | | +| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
getClient: (options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
getExporter: (client: SavedObjectsClientContract) => ISavedObjectsExporter;
getImporter: (client: SavedObjectsClientContract) => ISavedObjectsImporter;
};
elasticsearch: {
client: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
deprecations: {
client: DeprecationsClient;
};
} | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index a31e4512cfcf1b..5b884efe9909be 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -10,7 +10,7 @@ start(core: CoreStart, { fieldFormats }: DataPluginStartDependencies): { fieldFormats: FieldFormatsStart; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -28,7 +28,7 @@ start(core: CoreStart, { fieldFormats }: DataPluginStartDependencies): { `{ fieldFormats: FieldFormatsStart; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/package.json b/package.json index 205685cd5389c8..9b67448b67479d 100644 --- a/package.json +++ b/package.json @@ -227,7 +227,6 @@ "deepmerge": "^4.2.2", "del": "^5.1.0", "elastic-apm-node": "^3.16.0", - "elasticsearch": "^16.7.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", "expiry-js": "0.1.7", @@ -740,10 +739,10 @@ "gulp": "4.0.2", "gulp-babel": "^8.0.0", "gulp-brotli": "^3.0.0", + "gulp-gzip": "^1.4.2", "gulp-postcss": "^8.0.0", "gulp-sourcemaps": "2.6.5", "gulp-terser": "^2.0.1", - "gulp-gzip": "^1.4.2", "gulp-zip": "^5.0.2", "has-ansi": "^3.0.0", "hdr-histogram-js": "^1.2.0", @@ -829,8 +828,8 @@ "tar-fs": "^2.1.0", "tempy": "^0.3.0", "terminal-link": "^2.1.1", - "terser-webpack-plugin": "^2.1.2", "terser": "^5.7.1", + "terser-webpack-plugin": "^2.1.2", "ts-loader": "^7.0.5", "ts-morph": "^9.1.0", "tsd": "^0.13.1", diff --git a/src/core/server/core_route_handler_context.test.ts b/src/core/server/core_route_handler_context.test.ts index 3a54f7d55410e4..ace0144eae54fb 100644 --- a/src/core/server/core_route_handler_context.test.ts +++ b/src/core/server/core_route_handler_context.test.ts @@ -44,43 +44,6 @@ describe('#elasticsearch', () => { expect(client2).toBe(mockResult); }); }); - - describe('#legacy', () => { - describe('#client', () => { - test('returns the results of coreStart.elasticsearch.legacy.client.asScoped', () => { - const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); - const context = new CoreRouteHandlerContext(coreStart, request); - - const client = context.elasticsearch.legacy.client; - expect(client).toBe(coreStart.elasticsearch.legacy.client.asScoped.mock.results[0].value); - }); - - test('lazily created', () => { - const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); - const context = new CoreRouteHandlerContext(coreStart, request); - - expect(coreStart.elasticsearch.legacy.client.asScoped).not.toHaveBeenCalled(); - const client = context.elasticsearch.legacy.client; - expect(coreStart.elasticsearch.legacy.client.asScoped).toHaveBeenCalled(); - expect(client).toBeDefined(); - }); - - test('only creates one instance', () => { - const request = httpServerMock.createKibanaRequest(); - const coreStart = coreMock.createInternalStart(); - const context = new CoreRouteHandlerContext(coreStart, request); - - const client1 = context.elasticsearch.legacy.client; - const client2 = context.elasticsearch.legacy.client; - expect(coreStart.elasticsearch.legacy.client.asScoped.mock.calls.length).toBe(1); - const mockResult = coreStart.elasticsearch.legacy.client.asScoped.mock.results[0].value; - expect(client1).toBe(mockResult); - expect(client2).toBe(mockResult); - }); - }); - }); }); describe('#savedObjects', () => { diff --git a/src/core/server/core_route_handler_context.ts b/src/core/server/core_route_handler_context.ts index 1f79a24046d261..3106053eb6afae 100644 --- a/src/core/server/core_route_handler_context.ts +++ b/src/core/server/core_route_handler_context.ts @@ -15,19 +15,12 @@ import { ISavedObjectTypeRegistry, SavedObjectsClientProviderOptions, } from './saved_objects'; -import { - InternalElasticsearchServiceStart, - IScopedClusterClient, - LegacyScopedClusterClient, -} from './elasticsearch'; +import { InternalElasticsearchServiceStart, IScopedClusterClient } from './elasticsearch'; import { InternalUiSettingsServiceStart, IUiSettingsClient } from './ui_settings'; import { DeprecationsClient, InternalDeprecationsServiceStart } from './deprecations'; class CoreElasticsearchRouteHandlerContext { #client?: IScopedClusterClient; - #legacy?: { - client: Pick; - }; constructor( private readonly elasticsearchStart: InternalElasticsearchServiceStart, @@ -40,15 +33,6 @@ class CoreElasticsearchRouteHandlerContext { } return this.#client; } - - public get legacy() { - if (this.#legacy == null) { - this.#legacy = { - client: this.elasticsearchStart.legacy.client.asScoped(this.request), - }; - } - return this.#legacy; - } } class CoreSavedObjectsRouteHandlerContext { diff --git a/src/core/server/elasticsearch/client/mocks.ts b/src/core/server/elasticsearch/client/mocks.ts index 26a68df81f24e1..5e2bf784b2a1d4 100644 --- a/src/core/server/elasticsearch/client/mocks.ts +++ b/src/core/server/elasticsearch/client/mocks.ts @@ -6,20 +6,23 @@ * Side Public License, v 1. */ -import { Client, ApiResponse } from '@elastic/elasticsearch'; +import type { Client, ApiResponse } from '@elastic/elasticsearch'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { ElasticsearchClient } from './types'; import { ICustomClusterClient } from './cluster_client'; import { PRODUCT_RESPONSE_HEADER } from '../supported_server_response_check'; +// use jest.requireActual() to prevent weird errors when people mock @elastic/elasticsearch +const { Client: UnmockedClient } = jest.requireActual('@elastic/elasticsearch'); + const createInternalClientMock = ( res?: MockedTransportRequestPromise ): DeeplyMockedKeys => { // we mimic 'reflection' on a concrete instance of the client to generate the mocked functions. - const client = new Client({ + const client = new UnmockedClient({ node: 'http://localhost', - }) as any; + }); const omittedProps = [ '_events', diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index 0ccc0f51f6abd5..8d70e0bcbd0661 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -9,14 +9,12 @@ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@kbn/utility-types'; -import { ILegacyClusterClient, ILegacyCustomClusterClient } from './legacy'; import { elasticsearchClientMock, ClusterClientMock, CustomClusterClientMock, } from './client/mocks'; import { ElasticsearchClientConfig } from './client'; -import { legacyClientMock } from './legacy/mocks'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; import { @@ -32,8 +30,6 @@ type MockedElasticSearchServicePreboot = jest.Mocked; - createClient: jest.Mock; - client: jest.Mocked; }; } @@ -59,14 +55,8 @@ const createSetupContractMock = () => { const setupContract: MockedElasticSearchServiceSetup = { legacy: { config$: new BehaviorSubject({} as ElasticsearchConfig), - createClient: jest.fn(), - client: legacyClientMock.createClusterClient(), }, }; - setupContract.legacy.createClient.mockReturnValue(legacyClientMock.createCustomClusterClient()); - setupContract.legacy.client.asScoped.mockReturnValue( - legacyClientMock.createScopedClusterClient() - ); return setupContract; }; @@ -76,14 +66,9 @@ const createStartContractMock = () => { createClient: jest.fn(), legacy: { config$: new BehaviorSubject({} as ElasticsearchConfig), - createClient: jest.fn(), - client: legacyClientMock.createClusterClient(), }, }; - startContract.legacy.createClient.mockReturnValue(legacyClientMock.createCustomClusterClient()); - startContract.legacy.client.asScoped.mockReturnValue( - legacyClientMock.createScopedClusterClient() - ); + startContract.createClient.mockImplementation(() => elasticsearchClientMock.createCustomClusterClient() ); @@ -92,11 +77,7 @@ const createStartContractMock = () => { const createInternalPrebootContractMock = createPrebootContractMock; -type MockedInternalElasticSearchServiceSetup = jest.Mocked< - InternalElasticsearchServiceSetup & { - legacy: { client: jest.Mocked }; - } ->; +type MockedInternalElasticSearchServiceSetup = jest.Mocked; const createInternalSetupContractMock = () => { const setupContract: MockedInternalElasticSearchServiceSetup = { esNodesCompatibility$: new BehaviorSubject({ @@ -113,9 +94,6 @@ const createInternalSetupContractMock = () => { ...createSetupContractMock().legacy, }, }; - setupContract.legacy.client.asScoped.mockReturnValue( - legacyClientMock.createScopedClusterClient() - ); return setupContract; }; @@ -144,10 +122,6 @@ export const elasticsearchServiceMock = { createSetup: createSetupContractMock, createInternalStart: createInternalStartContractMock, createStart: createStartContractMock, - createLegacyClusterClient: legacyClientMock.createClusterClient, - createLegacyCustomClusterClient: legacyClientMock.createCustomClusterClient, - createLegacyScopedClusterClient: legacyClientMock.createScopedClusterClient, - createLegacyElasticsearchClient: legacyClientMock.createElasticsearchClient, ...elasticsearchClientMock, }; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts b/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts index e42fe76e1d9929..b1a60019a801fb 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts @@ -6,8 +6,5 @@ * Side Public License, v 1. */ -export const MockLegacyClusterClient = jest.fn(); -jest.mock('./legacy/cluster_client', () => ({ LegacyClusterClient: MockLegacyClusterClient })); - export const MockClusterClient = jest.fn(); jest.mock('./client/cluster_client', () => ({ ClusterClient: MockClusterClient })); diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 8932a4c73e1f2a..2f1883fd8646af 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { MockLegacyClusterClient, MockClusterClient } from './elasticsearch_service.test.mocks'; +import { MockClusterClient } from './elasticsearch_service.test.mocks'; import { BehaviorSubject } from 'rxjs'; import { first } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/dev-utils'; @@ -18,7 +18,6 @@ import { httpServiceMock } from '../http/http_service.mock'; import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; import { configSchema, ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; -import { elasticsearchServiceMock } from './elasticsearch_service.mock'; import { elasticsearchClientMock } from './client/mocks'; import { duration } from 'moment'; @@ -37,9 +36,7 @@ let coreContext: CoreContext; const logger = loggingSystemMock.create(); let mockClusterClientInstance: ReturnType; -let mockLegacyClusterClientInstance: ReturnType< - typeof elasticsearchServiceMock.createLegacyCustomClusterClient ->; + let mockConfig$: BehaviorSubject; beforeEach(() => { env = Env.createDefault(REPO_ROOT, getEnvOptions()); @@ -58,11 +55,7 @@ beforeEach(() => { coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; elasticsearchService = new ElasticsearchService(coreContext); - MockLegacyClusterClient.mockClear(); MockClusterClient.mockClear(); - - mockLegacyClusterClientInstance = elasticsearchServiceMock.createLegacyCustomClusterClient(); - MockLegacyClusterClient.mockImplementation(() => mockLegacyClusterClientInstance); mockClusterClientInstance = elasticsearchClientMock.createCustomClusterClient(); MockClusterClient.mockImplementation(() => mockClusterClientInstance); }); @@ -162,141 +155,6 @@ describe('#setup', () => { ); }); - it('returns legacy elasticsearch client as a part of the contract', async () => { - const setupContract = await elasticsearchService.setup(setupDeps); - const client = setupContract.legacy.client; - - expect(mockLegacyClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); - await client.callAsInternalUser('any'); - expect(mockLegacyClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); - }); - - describe('#createLegacyClient', () => { - it('allows to specify config properties', async () => { - const setupContract = await elasticsearchService.setup(setupDeps); - - // reset all mocks called during setup phase - MockLegacyClusterClient.mockClear(); - - const customConfig = { keepAlive: true }; - const clusterClient = setupContract.legacy.createClient('some-custom-type', customConfig); - - expect(clusterClient).toBe(mockLegacyClusterClientInstance); - - expect(MockLegacyClusterClient).toHaveBeenCalledWith( - expect.objectContaining(customConfig), - expect.objectContaining({ context: ['elasticsearch'] }), - 'some-custom-type', - expect.any(Function) - ); - }); - - it('falls back to elasticsearch default config values if property not specified', async () => { - const setupContract = await elasticsearchService.setup(setupDeps); - - // reset all mocks called during setup phase - MockLegacyClusterClient.mockClear(); - - const customConfig = { - hosts: ['http://8.8.8.8'], - logQueries: true, - ssl: { certificate: 'certificate-value' }, - }; - setupContract.legacy.createClient('some-custom-type', customConfig); - - const config = MockLegacyClusterClient.mock.calls[0][0]; - expect(config).toMatchInlineSnapshot(` - Object { - "healthCheckDelay": "PT0.01S", - "hosts": Array [ - "http://8.8.8.8", - ], - "logQueries": true, - "requestHeadersWhitelist": Array [ - undefined, - ], - "ssl": Object { - "certificate": "certificate-value", - "verificationMode": "none", - }, - } - `); - }); - it('falls back to elasticsearch config if custom config not passed', async () => { - const setupContract = await elasticsearchService.setup(setupDeps); - - // reset all mocks called during setup phase - MockLegacyClusterClient.mockClear(); - - setupContract.legacy.createClient('another-type'); - - const config = MockLegacyClusterClient.mock.calls[0][0]; - expect(config).toMatchInlineSnapshot(` - Object { - "healthCheckDelay": "PT0.01S", - "hosts": Array [ - "http://1.2.3.4", - ], - "requestHeadersWhitelist": Array [ - undefined, - ], - "ssl": Object { - "alwaysPresentCertificate": undefined, - "certificate": undefined, - "certificateAuthorities": undefined, - "key": undefined, - "keyPassphrase": undefined, - "verificationMode": "none", - }, - } - `); - }); - - it('does not merge elasticsearch hosts if custom config overrides', async () => { - configService.atPath.mockReturnValueOnce( - new BehaviorSubject({ - hosts: ['http://1.2.3.4', 'http://9.8.7.6'], - healthCheck: { - delay: duration(2000), - }, - ssl: { - verificationMode: 'none', - }, - } as any) - ); - elasticsearchService = new ElasticsearchService(coreContext); - const setupContract = await elasticsearchService.setup(setupDeps); - - // reset all mocks called during setup phase - MockLegacyClusterClient.mockClear(); - - const customConfig = { - hosts: ['http://8.8.8.8'], - logQueries: true, - ssl: { certificate: 'certificate-value' }, - }; - setupContract.legacy.createClient('some-custom-type', customConfig); - - const config = MockLegacyClusterClient.mock.calls[0][0]; - expect(config).toMatchInlineSnapshot(` - Object { - "healthCheckDelay": "PT2S", - "hosts": Array [ - "http://8.8.8.8", - ], - "logQueries": true, - "requestHeadersWhitelist": Array [ - undefined, - ], - "ssl": Object { - "certificate": "certificate-value", - "verificationMode": "none", - }, - } - `); - }); - }); - it('esNodeVersionCompatibility$ only starts polling when subscribed to', async (done) => { const mockedClient = mockClusterClientInstance.asInternalUser; mockedClient.nodes.info.mockImplementation(() => @@ -419,7 +277,6 @@ describe('#stop', () => { await elasticsearchService.start(); await elasticsearchService.stop(); - expect(mockLegacyClusterClientInstance.close).toHaveBeenCalledTimes(1); expect(mockClusterClientInstance.close).toHaveBeenCalledTimes(1); }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index acd2204334c0e9..ce48f49b686600 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -13,11 +13,7 @@ import { merge } from '@kbn/std'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { - LegacyClusterClient, - ILegacyCustomClusterClient, - LegacyElasticsearchClientConfig, -} from './legacy'; + import { ClusterClient, ElasticsearchClientConfig } from './client'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; import type { InternalHttpServiceSetup, GetAuthHeaders } from '../http'; @@ -45,12 +41,6 @@ export class ElasticsearchService private getAuthHeaders?: GetAuthHeaders; private executionContextClient?: IExecutionContext; - private createLegacyCustomClient?: ( - type: string, - clientConfig?: Partial - ) => ILegacyCustomClusterClient; - private legacyClient?: LegacyClusterClient; - private client?: ClusterClient; constructor(private readonly coreContext: CoreContext) { @@ -84,7 +74,6 @@ export class ElasticsearchService this.getAuthHeaders = deps.http.getAuthHeaders; this.executionContextClient = deps.executionContext; - this.legacyClient = this.createLegacyClusterClient('data', config); this.client = this.createClusterClient('data', config); const esNodesCompatibility$ = pollEsNodesVersion({ @@ -95,23 +84,16 @@ export class ElasticsearchService kibanaVersion: this.kibanaVersion, }).pipe(takeUntil(this.stop$), shareReplay({ refCount: true, bufferSize: 1 })); - this.createLegacyCustomClient = (type, clientConfig = {}) => { - const finalConfig = merge({}, config, clientConfig); - return this.createLegacyClusterClient(type, finalConfig); - }; - return { legacy: { config$: this.config$, - client: this.legacyClient, - createClient: this.createLegacyCustomClient, }, esNodesCompatibility$, status$: calculateStatus$(esNodesCompatibility$), }; } public async start(): Promise { - if (!this.legacyClient || !this.createLegacyCustomClient) { + if (!this.client) { throw new Error('ElasticsearchService needs to be setup before calling start'); } @@ -121,8 +103,6 @@ export class ElasticsearchService createClient: (type, clientConfig) => this.createClusterClient(type, config, clientConfig), legacy: { config$: this.config$, - client: this.legacyClient, - createClient: this.createLegacyCustomClient, }, }; } @@ -133,9 +113,6 @@ export class ElasticsearchService if (this.client) { await this.client.close(); } - if (this.legacyClient) { - this.legacyClient.close(); - } } private createClusterClient( @@ -152,13 +129,4 @@ export class ElasticsearchService () => this.executionContextClient?.getAsHeader() ); } - - private createLegacyClusterClient(type: string, config: LegacyElasticsearchClientConfig) { - return new LegacyClusterClient( - config, - this.coreContext.logger.get('elasticsearch'), - type, - this.getAuthHeaders - ); - } } diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index f50e3a0f72860f..7f0620a03e5f4f 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -22,7 +22,6 @@ export type { ScopeableRequest, ElasticsearchConfigPreboot, } from './types'; -export * from './legacy'; export type { IClusterClient, ICustomClusterClient, diff --git a/src/core/server/elasticsearch/legacy/api_types.ts b/src/core/server/elasticsearch/legacy/api_types.ts deleted file mode 100644 index e4ff4816527b48..00000000000000 --- a/src/core/server/elasticsearch/legacy/api_types.ts +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - Client, - GenericParams, - // root params - BulkIndexDocumentsParams, - ClearScrollParams, - CountParams, - CreateDocumentParams, - DeleteDocumentParams, - DeleteDocumentByQueryParams, - DeleteScriptParams, - DeleteTemplateParams, - ExistsParams, - ExplainParams, - FieldStatsParams, - GetParams, - GetResponse, - GetScriptParams, - GetSourceParams, - GetTemplateParams, - IndexDocumentParams, - InfoParams, - MGetParams, - MSearchParams, - MSearchTemplateParams, - MTermVectorsParams, - PingParams, - PutScriptParams, - PutTemplateParams, - ReindexParams, - ReindexRethrottleParams, - RenderSearchTemplateParams, - ScrollParams, - SearchParams, - SearchShardsParams, - SearchTemplateParams, - SuggestParams, - TermvectorsParams, - UpdateDocumentParams, - UpdateDocumentByQueryParams, - MGetResponse, - MSearchResponse, - SearchResponse, - // cat - CatAliasesParams, - CatAllocationParams, - CatFielddataParams, - CatHealthParams, - CatHelpParams, - CatIndicesParams, - CatCommonParams, - CatRecoveryParams, - CatSegmentsParams, - CatShardsParams, - CatSnapshotsParams, - CatTasksParams, - CatThreadPoolParams, - // cluster - ClusterAllocationExplainParams, - ClusterGetSettingsParams, - ClusterHealthParams, - ClusterPendingTasksParams, - ClusterPutSettingsParams, - ClusterRerouteParams, - ClusterStateParams, - ClusterStatsParams, - // indices - IndicesAnalyzeParams, - IndicesClearCacheParams, - IndicesCloseParams, - IndicesCreateParams, - IndicesDeleteParams, - IndicesDeleteAliasParams, - IndicesDeleteTemplateParams, - IndicesExistsParams, - IndicesExistsAliasParams, - IndicesExistsTemplateParams, - IndicesExistsTypeParams, - IndicesFlushParams, - IndicesFlushSyncedParams, - IndicesForcemergeParams, - IndicesGetParams, - IndicesGetAliasParams, - IndicesGetFieldMappingParams, - IndicesGetMappingParams, - IndicesGetSettingsParams, - IndicesGetTemplateParams, - IndicesGetUpgradeParams, - IndicesOpenParams, - IndicesPutAliasParams, - IndicesPutMappingParams, - IndicesPutSettingsParams, - IndicesPutTemplateParams, - IndicesRecoveryParams, - IndicesRefreshParams, - IndicesRolloverParams, - IndicesSegmentsParams, - IndicesShardStoresParams, - IndicesShrinkParams, - IndicesStatsParams, - IndicesUpdateAliasesParams, - IndicesUpgradeParams, - IndicesValidateQueryParams, - // ingest - IngestDeletePipelineParams, - IngestGetPipelineParams, - IngestPutPipelineParams, - IngestSimulateParams, - // nodes - NodesHotThreadsParams, - NodesInfoParams, - NodesStatsParams, - // snapshot - SnapshotCreateParams, - SnapshotCreateRepositoryParams, - SnapshotDeleteParams, - SnapshotDeleteRepositoryParams, - SnapshotGetParams, - SnapshotGetRepositoryParams, - SnapshotRestoreParams, - SnapshotStatusParams, - SnapshotVerifyRepositoryParams, - // tasks - TasksCancelParams, - TasksGetParams, - TasksListParams, -} from 'elasticsearch'; - -/** - * The set of options that defines how API call should be made and result be - * processed. - * - * @public - * @deprecated - * @removeBy 7.16 - */ -export interface LegacyCallAPIOptions { - /** - * Indicates whether `401 Unauthorized` errors returned from the Elasticsearch API - * should be wrapped into `Boom` error instances with properly set `WWW-Authenticate` - * header that could have been returned by the API itself. If API didn't specify that - * then `Basic realm="Authorization Required"` is used as `WWW-Authenticate`. - */ - wrap401Errors?: boolean; - /** - * A signal object that allows you to abort the request via an AbortController object. - */ - signal?: AbortSignal; -} - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export interface LegacyAPICaller { - /* eslint-disable */ - (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'clearScroll', params: ClearScrollParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'count', params: CountParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'create', params: CreateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'delete', params: DeleteDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'deleteScript', params: DeleteScriptParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'exists', params: ExistsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'explain', params: ExplainParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'fieldStats', params: FieldStatsParams, options?: LegacyCallAPIOptions): ReturnType; - // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'get', params: GetParams, options?: LegacyCallAPIOptions): Promise>; - (endpoint: 'getScript', params: GetScriptParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'getSource', params: GetSourceParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'getTemplate', params: GetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'index', params: IndexDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'info', params: InfoParams, options?: LegacyCallAPIOptions): ReturnType; - // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'mget', params: MGetParams, options?: LegacyCallAPIOptions): Promise>; - (endpoint: 'msearch', params: MSearchParams, options?: LegacyCallAPIOptions): Promise>; - (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: LegacyCallAPIOptions): Promise>; - (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'ping', params: PingParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'putScript', params: PutScriptParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'putTemplate', params: PutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'reindex', params: ReindexParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'scroll', params: ScrollParams, options?: LegacyCallAPIOptions): Promise>; - (endpoint: 'search', params: SearchParams, options?: LegacyCallAPIOptions): Promise>; - (endpoint: 'searchShards', params: SearchShardsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'suggest', params: SuggestParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'termvectors', params: TermvectorsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'update', params: UpdateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; - - // cat namespace - (endpoint: 'cat.aliases', params: CatAliasesParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.allocation', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.count', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.health', params: CatHealthParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.help', params: CatHelpParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.indices', params: CatIndicesParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.master', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.nodes', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.plugins', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.repositories', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.segments', params: CatSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.shards', params: CatShardsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.tasks', params: CatTasksParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: LegacyCallAPIOptions): ReturnType; - - // cluster namespace - (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cluster.health', params: ClusterHealthParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cluster.state', params: ClusterStateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: LegacyCallAPIOptions): ReturnType; - - // indices namespace - (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.close', params: IndicesCloseParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.create', params: IndicesCreateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.exists', params: IndicesExistsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.flush', params: IndicesFlushParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.get', params: IndicesGetParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.open', params: IndicesOpenParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.stats', params: IndicesStatsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: LegacyCallAPIOptions): ReturnType; - - // ingest namepsace - (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: LegacyCallAPIOptions): ReturnType; - - // nodes namespace - (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'nodes.info', params: NodesInfoParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'nodes.stats', params: NodesStatsParams, options?: LegacyCallAPIOptions): ReturnType; - - // snapshot namespace - (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - - // tasks namespace - (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'tasks.get', params: TasksGetParams, options?: LegacyCallAPIOptions): ReturnType; - (endpoint: 'tasks.list', params: TasksListParams, options?: LegacyCallAPIOptions): ReturnType; - - // other APIs accessed via transport.request - (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: LegacyCallAPIOptions): Promise< - AssistanceAPIResponse - >; - (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: LegacyCallAPIOptions): Promise< - DeprecationAPIResponse - >; - - // Catch-all definition - (endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; - /* eslint-enable */ -} - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export interface AssistantAPIClientParams extends GenericParams { - path: '/_migration/assistance'; - method: 'GET'; -} - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export interface AssistanceAPIResponse { - indices: { - [indexName: string]: { - action_required: MIGRATION_ASSISTANCE_INDEX_ACTION; - }; - }; -} - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export interface DeprecationAPIClientParams extends GenericParams { - path: '/_migration/deprecations'; - method: 'GET'; -} - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export interface DeprecationInfo { - level: MIGRATION_DEPRECATION_LEVEL; - message: string; - url: string; - details?: string; -} - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export interface IndexSettingsDeprecationInfo { - [indexName: string]: DeprecationInfo[]; -} - -/** - * @deprecated - * @removeBy 7.16 - * @public - * */ -export interface DeprecationAPIResponse { - cluster_settings: DeprecationInfo[]; - ml_settings: DeprecationInfo[]; - node_settings: DeprecationInfo[]; - index_settings: IndexSettingsDeprecationInfo; -} diff --git a/src/core/server/elasticsearch/legacy/cluster_client.test.mocks.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.mocks.ts deleted file mode 100644 index 73f0fb3216eeef..00000000000000 --- a/src/core/server/elasticsearch/legacy/cluster_client.test.mocks.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const MockClient = jest.fn(); -jest.mock('elasticsearch', () => { - const original = jest.requireActual('elasticsearch'); - - return { - ...original, - Client: MockClient, - }; -}); - -export const MockScopedClusterClient = jest.fn(); -jest.mock('./scoped_cluster_client', () => ({ - LegacyScopedClusterClient: MockScopedClusterClient, -})); - -export const mockParseElasticsearchClientConfig = jest.fn(); -jest.mock('./elasticsearch_client_config', () => ({ - parseElasticsearchClientConfig: mockParseElasticsearchClientConfig, -})); diff --git a/src/core/server/elasticsearch/legacy/cluster_client.test.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.ts deleted file mode 100644 index 52bc4bd45660e2..00000000000000 --- a/src/core/server/elasticsearch/legacy/cluster_client.test.ts +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ElasticsearchConfig } from '../elasticsearch_config'; - -import { - MockClient, - mockParseElasticsearchClientConfig, - MockScopedClusterClient, -} from './cluster_client.test.mocks'; - -import { errors } from 'elasticsearch'; -import { get } from 'lodash'; -import { Logger } from '../../logging'; -import { loggingSystemMock } from '../../logging/logging_system.mock'; -import { httpServerMock } from '../../http/http_server.mocks'; -import { LegacyClusterClient } from './cluster_client'; - -const logger = loggingSystemMock.create(); -afterEach(() => jest.clearAllMocks()); - -test('#constructor creates client with parsed config', () => { - const mockEsClientConfig = { apiVersion: 'es-client-master' }; - mockParseElasticsearchClientConfig.mockReturnValue(mockEsClientConfig); - - const mockEsConfig = { apiVersion: 'es-version' } as any; - const mockLogger = logger.get(); - - const clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - expect(clusterClient).toBeDefined(); - - expect(mockParseElasticsearchClientConfig).toHaveBeenCalledTimes(1); - expect(mockParseElasticsearchClientConfig).toHaveBeenLastCalledWith( - mockEsConfig, - mockLogger, - 'custom-type' - ); - - expect(MockClient).toHaveBeenCalledTimes(1); - expect(MockClient).toHaveBeenCalledWith(mockEsClientConfig); -}); - -describe('#callAsInternalUser', () => { - let mockEsClientInstance: { - close: jest.Mock; - ping: jest.Mock; - security: { authenticate: jest.Mock }; - }; - let clusterClient: LegacyClusterClient; - - beforeEach(() => { - mockEsClientInstance = { - close: jest.fn(), - ping: jest.fn(), - security: { authenticate: jest.fn() }, - }; - MockClient.mockImplementation(() => mockEsClientInstance); - - clusterClient = new LegacyClusterClient( - { apiVersion: 'es-version' } as any, - logger.get(), - 'custom-type' - ); - }); - - test('fails if cluster client is closed', async () => { - clusterClient.close(); - - await expect( - clusterClient.callAsInternalUser('ping', {}) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cluster client cannot be used after it has been closed."` - ); - }); - - test('fails if endpoint is invalid', async () => { - await expect( - clusterClient.callAsInternalUser('pong', {}) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"called with an invalid endpoint: pong"`); - }); - - test('correctly deals with top level endpoint', async () => { - const mockResponse = { data: 'ping' }; - const mockParams = { param: 'ping' }; - mockEsClientInstance.ping.mockImplementation(function mockCall(this: any) { - return Promise.resolve({ - context: this, - response: mockResponse, - }); - }); - - const mockResult = await clusterClient.callAsInternalUser('ping', mockParams); - expect(mockResult.response).toBe(mockResponse); - expect(mockResult.context).toBe(mockEsClientInstance); - expect(mockEsClientInstance.ping).toHaveBeenCalledTimes(1); - expect(mockEsClientInstance.ping).toHaveBeenLastCalledWith(mockParams); - }); - - test('sets the authorization header when a service account token is configured', async () => { - clusterClient = new LegacyClusterClient( - { apiVersion: 'es-version', serviceAccountToken: 'ABC123' } as any, - logger.get(), - 'custom-type' - ); - - const mockResponse = { data: 'ping' }; - const mockParams = { param: 'ping' }; - mockEsClientInstance.ping.mockImplementation(function mockCall(this: any) { - return Promise.resolve({ - context: this, - response: mockResponse, - }); - }); - - await clusterClient.callAsInternalUser('ping', mockParams); - - expect(mockEsClientInstance.ping).toHaveBeenCalledWith({ - headers: { authorization: 'Bearer ABC123' }, - param: 'ping', - }); - }); - - test('correctly deals with nested endpoint', async () => { - const mockResponse = { data: 'authenticate' }; - const mockParams = { param: 'authenticate' }; - mockEsClientInstance.security.authenticate.mockImplementation(function mockCall(this: any) { - return Promise.resolve({ - context: this, - response: mockResponse, - }); - }); - - const mockResult = await clusterClient.callAsInternalUser('security.authenticate', mockParams); - expect(mockResult.response).toBe(mockResponse); - expect(mockResult.context).toBe(mockEsClientInstance.security); - expect(mockEsClientInstance.security.authenticate).toHaveBeenCalledTimes(1); - expect(mockEsClientInstance.security.authenticate).toHaveBeenLastCalledWith(mockParams); - }); - - test('does not wrap errors if `wrap401Errors` is set to `false`', async () => { - const mockError = { message: 'some error' }; - mockEsClientInstance.ping.mockRejectedValue(mockError); - - await expect( - clusterClient.callAsInternalUser('ping', undefined, { wrap401Errors: false }) - ).rejects.toBe(mockError); - - const mockAuthenticationError = { message: 'authentication error', statusCode: 401 }; - mockEsClientInstance.ping.mockRejectedValue(mockAuthenticationError); - - await expect( - clusterClient.callAsInternalUser('ping', undefined, { wrap401Errors: false }) - ).rejects.toBe(mockAuthenticationError); - }); - - test('wraps 401 errors when `wrap401Errors` is set to `true` or unspecified', async () => { - const mockError = { message: 'some error' }; - mockEsClientInstance.ping.mockRejectedValue(mockError); - - await expect(clusterClient.callAsInternalUser('ping')).rejects.toBe(mockError); - await expect( - clusterClient.callAsInternalUser('ping', undefined, { wrap401Errors: true }) - ).rejects.toBe(mockError); - - const mockAuthorizationError = { message: 'authentication error', statusCode: 403 }; - mockEsClientInstance.ping.mockRejectedValue(mockAuthorizationError); - - await expect(clusterClient.callAsInternalUser('ping')).rejects.toBe(mockAuthorizationError); - await expect( - clusterClient.callAsInternalUser('ping', undefined, { wrap401Errors: true }) - ).rejects.toBe(mockAuthorizationError); - - const mockAuthenticationError = new (errors.AuthenticationException as any)( - 'Authentication Exception', - { statusCode: 401 } - ); - mockEsClientInstance.ping.mockRejectedValue(mockAuthenticationError); - - await expect(clusterClient.callAsInternalUser('ping')).rejects.toBe(mockAuthenticationError); - await expect( - clusterClient.callAsInternalUser('ping', undefined, { wrap401Errors: true }) - ).rejects.toStrictEqual(mockAuthenticationError); - }); - - test('aborts the request and rejects if a signal is provided and aborted', async () => { - const controller = new AbortController(); - - // The ES client returns a promise with an additional `abort` method to abort the request - const mockValue: any = Promise.resolve(); - mockValue.abort = jest.fn(); - mockEsClientInstance.ping.mockReturnValue(mockValue); - - const promise = clusterClient.callAsInternalUser('ping', undefined, { - wrap401Errors: false, - signal: controller.signal, - }); - - controller.abort(); - - expect(mockValue.abort).toHaveBeenCalled(); - await expect(promise).rejects.toThrowErrorMatchingInlineSnapshot(`"Request was aborted"`); - }); - - test('does not override WWW-Authenticate if returned by Elasticsearch', async () => { - const mockAuthenticationError = new (errors.AuthenticationException as any)( - 'Authentication Exception', - { statusCode: 401 } - ); - - const mockAuthenticationErrorWithHeader = new (errors.AuthenticationException as any)( - 'Authentication Exception', - { - body: { error: { header: { 'WWW-Authenticate': 'some custom header' } } }, - statusCode: 401, - } - ); - mockEsClientInstance.ping - .mockRejectedValueOnce(mockAuthenticationError) - .mockRejectedValueOnce(mockAuthenticationErrorWithHeader); - - await expect(clusterClient.callAsInternalUser('ping')).rejects.toBe(mockAuthenticationError); - expect(get(mockAuthenticationError, 'output.headers.WWW-Authenticate')).toBe( - 'Basic realm="Authorization Required"' - ); - - await expect(clusterClient.callAsInternalUser('ping')).rejects.toBe( - mockAuthenticationErrorWithHeader - ); - expect(get(mockAuthenticationErrorWithHeader, 'output.headers.WWW-Authenticate')).toBe( - 'some custom header' - ); - }); -}); - -describe('#asScoped', () => { - let mockEsClientInstance: { ping: jest.Mock; close: jest.Mock }; - let mockScopedEsClientInstance: { ping: jest.Mock; close: jest.Mock }; - - let clusterClient: LegacyClusterClient; - let mockLogger: Logger; - let mockEsConfig: ElasticsearchConfig; - - beforeEach(() => { - mockEsClientInstance = { ping: jest.fn(), close: jest.fn() }; - mockScopedEsClientInstance = { ping: jest.fn(), close: jest.fn() }; - MockClient.mockImplementationOnce(() => mockEsClientInstance).mockImplementationOnce( - () => mockScopedEsClientInstance - ); - - mockLogger = logger.get(); - mockEsConfig = { - apiVersion: 'es-version', - requestHeadersWhitelist: ['one', 'two'], - } as any; - - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - jest.clearAllMocks(); - }); - - test('creates additional Elasticsearch client only once', () => { - const firstScopedClusterClient = clusterClient.asScoped( - httpServerMock.createRawRequest({ headers: { one: '1' } }) - ); - - expect(firstScopedClusterClient).toBeDefined(); - expect(mockParseElasticsearchClientConfig).toHaveBeenCalledTimes(1); - expect(mockParseElasticsearchClientConfig).toHaveBeenLastCalledWith( - mockEsConfig, - mockLogger, - 'custom-type', - { - auth: false, - ignoreCertAndKey: true, - } - ); - - expect(MockClient).toHaveBeenCalledTimes(1); - expect(MockClient).toHaveBeenCalledWith( - mockParseElasticsearchClientConfig.mock.results[0].value - ); - - jest.clearAllMocks(); - - const secondScopedClusterClient = clusterClient.asScoped( - httpServerMock.createRawRequest({ headers: { two: '2' } }) - ); - - expect(secondScopedClusterClient).toBeDefined(); - expect(secondScopedClusterClient).not.toBe(firstScopedClusterClient); - expect(mockParseElasticsearchClientConfig).not.toHaveBeenCalled(); - expect(MockClient).not.toHaveBeenCalled(); - }); - - test('properly configures `ignoreCertAndKey` for various configurations', () => { - // Config without SSL. - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - - mockParseElasticsearchClientConfig.mockClear(); - clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); - - expect(mockParseElasticsearchClientConfig).toHaveBeenCalledTimes(1); - expect(mockParseElasticsearchClientConfig).toHaveBeenLastCalledWith( - mockEsConfig, - mockLogger, - 'custom-type', - { - auth: false, - ignoreCertAndKey: true, - } - ); - - // Config ssl.alwaysPresentCertificate === false - mockEsConfig = { ...mockEsConfig, ssl: { alwaysPresentCertificate: false } } as any; - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - - mockParseElasticsearchClientConfig.mockClear(); - clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); - - expect(mockParseElasticsearchClientConfig).toHaveBeenCalledTimes(1); - expect(mockParseElasticsearchClientConfig).toHaveBeenLastCalledWith( - mockEsConfig, - mockLogger, - 'custom-type', - { - auth: false, - ignoreCertAndKey: true, - } - ); - - // Config ssl.alwaysPresentCertificate === true - mockEsConfig = { ...mockEsConfig, ssl: { alwaysPresentCertificate: true } } as any; - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - - mockParseElasticsearchClientConfig.mockClear(); - clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); - - expect(mockParseElasticsearchClientConfig).toHaveBeenCalledTimes(1); - expect(mockParseElasticsearchClientConfig).toHaveBeenLastCalledWith( - mockEsConfig, - mockLogger, - 'custom-type', - { - auth: false, - ignoreCertAndKey: false, - } - ); - }); - - test('passes only filtered headers to the scoped cluster client', () => { - clusterClient.asScoped( - httpServerMock.createRawRequest({ headers: { zero: '0', one: '1', two: '2', three: '3' } }) - ); - - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - { one: '1', two: '2' } - ); - }); - - test('passes x-opaque-id header with request id', () => { - clusterClient.asScoped( - httpServerMock.createKibanaRequest({ - kibanaRequestState: { requestId: 'alpha', requestUuid: 'ignore-this-id' }, - }) - ); - - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - { 'x-opaque-id': 'alpha' } - ); - }); - - test('does not set the authorization header when a service account token is configured', async () => { - clusterClient = new LegacyClusterClient( - { - apiVersion: 'es-version', - requestHeadersWhitelist: ['zero'], - serviceAccountToken: 'ABC123', - } as any, - logger.get(), - 'custom-type' - ); - - clusterClient.asScoped( - httpServerMock.createRawRequest({ headers: { zero: '0', one: '1', two: '2', three: '3' } }) - ); - - const expectedHeaders = { zero: '0' }; - - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - expectedHeaders - ); - }); - - test('both scoped and internal API caller fail if cluster client is closed', async () => { - clusterClient.asScoped( - httpServerMock.createRawRequest({ headers: { zero: '0', one: '1', two: '2', three: '3' } }) - ); - - clusterClient.close(); - - const [[internalAPICaller, scopedAPICaller]] = MockScopedClusterClient.mock.calls; - await expect(internalAPICaller('ping')).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cluster client cannot be used after it has been closed."` - ); - - await expect(scopedAPICaller('ping', {})).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cluster client cannot be used after it has been closed."` - ); - }); - - test('does not fail when scope to not defined request', async () => { - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - clusterClient.asScoped(); - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - {} - ); - }); - - test('does not fail when scope to a request without headers', async () => { - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - clusterClient.asScoped({} as any); - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - {} - ); - }); - - test('calls getAuthHeaders and filters results for a real request', async () => { - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type', () => ({ - one: '1', - three: '3', - })); - clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { two: '2' } })); - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - { one: '1', two: '2' } - ); - }); - - test('getAuthHeaders results rewrite extends a request headers', async () => { - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type', () => ({ - one: 'foo', - })); - clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1', two: '2' } })); - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - { one: 'foo', two: '2' } - ); - }); - - test("doesn't call getAuthHeaders for a fake request", async () => { - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type', () => ({})); - clusterClient.asScoped({ headers: { one: 'foo' } }); - - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - { one: 'foo' } - ); - }); - - test('filters a fake request headers', async () => { - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, 'custom-type'); - clusterClient.asScoped({ headers: { one: '1', two: '2', three: '3' } }); - - expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); - expect(MockScopedClusterClient).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Function), - { one: '1', two: '2' } - ); - }); -}); - -describe('#close', () => { - let mockEsClientInstance: { close: jest.Mock }; - let mockScopedEsClientInstance: { close: jest.Mock }; - - let clusterClient: LegacyClusterClient; - - beforeEach(() => { - mockEsClientInstance = { close: jest.fn() }; - mockScopedEsClientInstance = { close: jest.fn() }; - MockClient.mockImplementationOnce(() => mockEsClientInstance).mockImplementationOnce( - () => mockScopedEsClientInstance - ); - - clusterClient = new LegacyClusterClient( - { apiVersion: 'es-version', requestHeadersWhitelist: [] } as any, - logger.get(), - 'custom-type' - ); - }); - - test('closes underlying Elasticsearch client', () => { - expect(mockEsClientInstance.close).not.toHaveBeenCalled(); - - clusterClient.close(); - expect(mockEsClientInstance.close).toHaveBeenCalledTimes(1); - }); - - test('closes both internal and scoped underlying Elasticsearch clients', () => { - clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); - - expect(mockEsClientInstance.close).not.toHaveBeenCalled(); - expect(mockScopedEsClientInstance.close).not.toHaveBeenCalled(); - - clusterClient.close(); - expect(mockEsClientInstance.close).toHaveBeenCalledTimes(1); - expect(mockScopedEsClientInstance.close).toHaveBeenCalledTimes(1); - }); - - test('does not call close on already closed client', () => { - clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); - - clusterClient.close(); - mockEsClientInstance.close.mockClear(); - mockScopedEsClientInstance.close.mockClear(); - - clusterClient.close(); - expect(mockEsClientInstance.close).not.toHaveBeenCalled(); - expect(mockScopedEsClientInstance.close).not.toHaveBeenCalled(); - }); -}); diff --git a/src/core/server/elasticsearch/legacy/cluster_client.ts b/src/core/server/elasticsearch/legacy/cluster_client.ts deleted file mode 100644 index 6a6765b67da9f2..00000000000000 --- a/src/core/server/elasticsearch/legacy/cluster_client.ts +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Client } from 'elasticsearch'; -import { get } from 'lodash'; - -import { LegacyElasticsearchErrorHelpers } from './errors'; -import { GetAuthHeaders, isKibanaRequest, isRealRequest } from '../../http'; -import { filterHeaders, ensureRawRequest } from '../../http/router'; -import { Logger } from '../../logging'; -import { ScopeableRequest } from '../types'; -import { - LegacyElasticsearchClientConfig, - parseElasticsearchClientConfig, -} from './elasticsearch_client_config'; -import { LegacyScopedClusterClient, ILegacyScopedClusterClient } from './scoped_cluster_client'; -import { LegacyCallAPIOptions, LegacyAPICaller } from './api_types'; - -/** - * Support Legacy platform request for the period of migration. - * - * @public - */ - -const noop = () => undefined; - -/** - * Calls the Elasticsearch API endpoint with the specified parameters. - * @param client Raw Elasticsearch JS client instance to use. - * @param endpoint Name of the API endpoint to call. - * @param clientParams Parameters that will be directly passed to the - * Elasticsearch JS client. - * @param options Options that affect the way we call the API and process the result. - */ -const callAPI = async ( - client: Client, - endpoint: string, - clientParams: Record = {}, - options: LegacyCallAPIOptions = { wrap401Errors: true } -) => { - const clientPath = endpoint.split('.'); - const api: any = get(client, clientPath); - if (!api) { - throw new Error(`called with an invalid endpoint: ${endpoint}`); - } - - const apiContext = clientPath.length === 1 ? client : get(client, clientPath.slice(0, -1)); - try { - return await new Promise((resolve, reject) => { - const request = api.call(apiContext, clientParams); - if (options.signal) { - options.signal.addEventListener('abort', () => { - request.abort(); - reject(new Error('Request was aborted')); - }); - } - return request.then(resolve, reject); - }); - } catch (err) { - if (!options.wrap401Errors || err.statusCode !== 401) { - throw err; - } - - throw LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(err); - } -}; - -/** - * Represents an Elasticsearch cluster API client created by the platform. - * It allows to call API on behalf of the internal Kibana user and - * the actual user that is derived from the request headers (via `asScoped(...)`). - * - * See {@link LegacyClusterClient}. - * - * @deprecated Use {@link IClusterClient}. - * @removeBy 7.16 - * @public - */ -export type ILegacyClusterClient = Pick; - -/** - * Represents an Elasticsearch cluster API client created by a plugin. - * It allows to call API on behalf of the internal Kibana user and - * the actual user that is derived from the request headers (via `asScoped(...)`). - * - * See {@link LegacyClusterClient}. - * @deprecated Use {@link ICustomClusterClient}. - * @removeBy 7.16 - * @public - */ -export type ILegacyCustomClusterClient = Pick< - LegacyClusterClient, - 'callAsInternalUser' | 'close' | 'asScoped' ->; - -/** - * {@inheritDoc IClusterClient} - * @deprecated Use {@link IClusterClient}. - * @removeBy 7.16 - * @public - */ -export class LegacyClusterClient implements ILegacyClusterClient { - /** - * Raw Elasticsearch JS client that acts on behalf of the Kibana internal user. - */ - private readonly client: Client; - - /** - * Optional raw Elasticsearch JS client that is shared between all the scoped clients created - * from this cluster client. Every API call is attributed by the wh - */ - private scopedClient?: Client; - - /** - * Indicates whether this cluster client (and all internal raw Elasticsearch JS clients) has been closed. - */ - private isClosed = false; - - constructor( - private readonly config: LegacyElasticsearchClientConfig, - private readonly log: Logger, - private readonly type: string, - private readonly getAuthHeaders: GetAuthHeaders = noop - ) { - this.client = new Client(parseElasticsearchClientConfig(config, log, type)); - } - - /** - * Calls specified endpoint with provided clientParams on behalf of the - * Kibana internal user. - * See {@link LegacyAPICaller}. - * @deprecated Use {@link IClusterClient.asInternalUser}. - * - * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. - * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. - * @param options - Options that affect the way we call the API and process the result. - */ - public callAsInternalUser: LegacyAPICaller = async ( - endpoint: string, - clientParams: Record = {}, - options?: LegacyCallAPIOptions - ) => { - this.assertIsNotClosed(); - - if (this.config.serviceAccountToken) { - clientParams.headers = { - ...clientParams.headers, - authorization: `Bearer ${this.config.serviceAccountToken}`, - }; - } - - return await (callAPI.bind(null, this.client) as LegacyAPICaller)( - endpoint, - clientParams, - options - ); - }; - - /** - * Closes the cluster client. After that client cannot be used and one should - * create a new client instance to be able to interact with Elasticsearch API. - */ - public close() { - if (this.isClosed) { - return; - } - - this.isClosed = true; - this.client.close(); - - if (this.scopedClient !== undefined) { - this.scopedClient.close(); - } - } - - /** - * Creates an instance of {@link ILegacyScopedClusterClient} based on the configuration the - * current cluster client that exposes additional `callAsCurrentUser` method - * scoped to the provided req. Consumers shouldn't worry about closing - * scoped client instances, these will be automatically closed as soon as the - * original cluster client isn't needed anymore and closed. - * - * @param request - Request the `IScopedClusterClient` instance will be scoped to. - * Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform - */ - public asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient { - // It'd have been quite expensive to create and configure client for every incoming - // request since it involves parsing of the config, reading of the SSL certificate and - // key files etc. Moreover scoped client needs two Elasticsearch JS clients at the same - // time: one to support `callAsInternalUser` and another one for `callAsCurrentUser`. - // To reduce that overhead we create one scoped client per cluster client and share it - // between all scoped client instances. - if (this.scopedClient === undefined) { - this.scopedClient = new Client( - parseElasticsearchClientConfig(this.config, this.log, this.type, { - auth: false, - ignoreCertAndKey: !this.config.ssl || !this.config.ssl.alwaysPresentCertificate, - }) - ); - } - - return new LegacyScopedClusterClient( - this.callAsInternalUser, - this.callAsCurrentUser, - filterHeaders(this.getHeaders(request), [ - 'x-opaque-id', - ...this.config.requestHeadersWhitelist, - ]) - ); - } - - /** - * Calls specified endpoint with provided clientParams on behalf of the - * user initiated request to the Kibana server (via HTTP request headers). - * See {@link LegacyAPICaller}. - * - * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. - * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. - * @param options - Options that affect the way we call the API and process the result. - */ - private callAsCurrentUser: LegacyAPICaller = async ( - endpoint: string, - clientParams: Record = {}, - options?: LegacyCallAPIOptions - ) => { - this.assertIsNotClosed(); - - return await (callAPI.bind(null, this.scopedClient!) as LegacyAPICaller)( - endpoint, - clientParams, - options - ); - }; - - private assertIsNotClosed() { - if (this.isClosed) { - throw new Error('Cluster client cannot be used after it has been closed.'); - } - } - - private getHeaders(request?: ScopeableRequest): Record { - if (!isRealRequest(request)) { - return request && request.headers ? request.headers : {}; - } - const authHeaders = this.getAuthHeaders(request); - const requestHeaders = ensureRawRequest(request).headers; - const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {}; - - return { ...requestHeaders, ...requestIdHeaders, ...authHeaders }; - } -} diff --git a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts deleted file mode 100644 index a343c0d5d2ad15..00000000000000 --- a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { duration } from 'moment'; -import { loggingSystemMock } from '../../logging/logging_system.mock'; -import { - LegacyElasticsearchClientConfig, - parseElasticsearchClientConfig, -} from './elasticsearch_client_config'; -import { DEFAULT_HEADERS } from '../default_headers'; -const logger = loggingSystemMock.create(); -afterEach(() => jest.clearAllMocks()); - -test('parses minimally specified config', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'master', - customHeaders: { xsrf: 'something' }, - sniffOnStart: false, - sniffOnConnectionFault: false, - hosts: ['http://localhost/elasticsearch'], - requestHeadersWhitelist: [], - }, - logger.get(), - 'custom-type' - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "master", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "80", - "protocol": "http:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": false, - "sniffOnStart": false, - } - `); -}); - -test('parses fully specified config', () => { - const elasticsearchConfig: LegacyElasticsearchClientConfig = { - apiVersion: 'v7.0.0', - customHeaders: { xsrf: 'something' }, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: [ - 'http://localhost/elasticsearch', - 'http://domain.com:1234/elasticsearch', - 'https://es.local', - ], - requestHeadersWhitelist: [], - username: 'elastic', - password: 'changeme', - pingTimeout: 12345, - requestTimeout: 54321, - sniffInterval: 11223344, - ssl: { - verificationMode: 'certificate', - certificateAuthorities: ['content-of-ca-path-1', 'content-of-ca-path-2'], - certificate: 'content-of-certificate-path', - key: 'content-of-key-path', - keyPassphrase: 'key-pass', - alwaysPresentCertificate: true, - }, - }; - - const elasticsearchClientConfig = parseElasticsearchClientConfig( - elasticsearchConfig, - logger.get(), - 'custom-type' - ); - - // Check that original references aren't used. - for (const host of elasticsearchClientConfig.hosts) { - expect(elasticsearchConfig.customHeaders).not.toBe(host.headers); - } - - expect(elasticsearchConfig.ssl).not.toBe(elasticsearchClientConfig.ssl); - - expect(elasticsearchClientConfig).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "80", - "protocol": "http:", - "query": null, - }, - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "domain.com", - "path": "/elasticsearch", - "port": "1234", - "protocol": "http:", - "query": null, - }, - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "httpAuth": "elastic:changeme", - "keepAlive": true, - "log": [Function], - "pingTimeout": 12345, - "requestTimeout": 54321, - "sniffInterval": 11223344, - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": Array [ - "content-of-ca-path-1", - "content-of-ca-path-2", - ], - "cert": "content-of-certificate-path", - "checkServerIdentity": [Function], - "key": "content-of-key-path", - "passphrase": "key-pass", - "rejectUnauthorized": true, - }, - } - `); -}); - -test('parses config timeouts of moment.Duration type', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'master', - customHeaders: { xsrf: 'something' }, - sniffOnStart: false, - sniffOnConnectionFault: false, - pingTimeout: duration(100, 'ms'), - requestTimeout: duration(30, 's'), - sniffInterval: duration(1, 'minute'), - hosts: ['http://localhost:9200/elasticsearch'], - requestHeadersWhitelist: [], - }, - logger.get(), - 'custom-type' - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "master", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "9200", - "protocol": "http:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "pingTimeout": 100, - "requestTimeout": 30000, - "sniffInterval": 60000, - "sniffOnConnectionFault": false, - "sniffOnStart": false, - } - `); -}); - -describe('#auth', () => { - test('is not set if #auth = false even if username and password are provided', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: { xsrf: 'something' }, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['http://user:password@localhost/elasticsearch', 'https://es.local'], - username: 'elastic', - password: 'changeme', - requestHeadersWhitelist: [], - }, - logger.get(), - 'custom-type', - { auth: false } - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "80", - "protocol": "http:", - "query": null, - }, - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - } - `); - }); - - test('is not set if username is not specified', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: { xsrf: 'something' }, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - password: 'changeme', - }, - logger.get(), - 'custom-type', - { auth: true } - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - } - `); - }); - - test('is not set if password is not specified', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: { xsrf: 'something' }, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - username: 'elastic', - }, - logger.get(), - 'custom-type', - { auth: true } - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - } - `); - }); -}); - -describe('#serviceAccountToken', () => { - it('is set when #auth is true, and a token is provided', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: { xsrf: 'something' }, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - serviceAccountToken: 'ABC123', - }, - logger.get(), - 'custom-type', - { auth: true } - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "serviceAccountToken": "ABC123", - "sniffOnConnectionFault": true, - "sniffOnStart": true, - } - `); - }); - - it('is not set when #auth is true, and a token is not provided', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: { xsrf: 'something' }, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - }, - logger.get(), - 'custom-type', - { auth: true } - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - } - `); - }); - - it('is not set when #auth is false, and a token is provided', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: { xsrf: 'something' }, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - serviceAccountToken: 'ABC123', - }, - logger.get(), - 'custom-type', - { auth: false } - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - } - `); - }); -}); - -describe('#customHeaders', () => { - test('override the default headers', () => { - const headerKey = Object.keys(DEFAULT_HEADERS)[0]; - const parsedConfig = parseElasticsearchClientConfig( - { - apiVersion: 'master', - customHeaders: { [headerKey]: 'foo' }, - sniffOnStart: false, - sniffOnConnectionFault: false, - hosts: ['http://localhost/elasticsearch'], - requestHeadersWhitelist: [], - }, - logger.get(), - 'custom-type' - ); - expect(parsedConfig.hosts[0].headers).toEqual({ - [headerKey]: 'foo', - }); - }); -}); - -describe('#log', () => { - test('default logger', () => { - const parsedConfig = parseElasticsearchClientConfig( - { - apiVersion: 'master', - customHeaders: { xsrf: 'something' }, - sniffOnStart: false, - sniffOnConnectionFault: false, - hosts: ['http://localhost/elasticsearch'], - requestHeadersWhitelist: [], - }, - logger.get(), - 'custom-type' - ); - - const esLogger = new parsedConfig.log(); - esLogger.error('some-error'); - esLogger.warning('some-warning'); - - esLogger.trace('METHOD', { path: '/some-path' }, '?query=2', 'unknown', '304'); - - esLogger.info('some-info'); - esLogger.debug('some-debug'); - - expect(typeof esLogger.close).toBe('function'); - - expect(loggingSystemMock.collect(logger)).toMatchInlineSnapshot(` - Object { - "debug": Array [ - Array [ - "304 - METHOD /some-path - ?query=2", - ], - ], - "error": Array [ - Array [ - "some-error", - ], - ], - "fatal": Array [], - "info": Array [], - "log": Array [], - "trace": Array [], - "warn": Array [ - Array [ - "some-warning", - ], - ], - } - `); - }); - - test('custom logger', () => { - const customLogger = jest.fn(); - - const parsedConfig = parseElasticsearchClientConfig( - { - apiVersion: 'master', - customHeaders: { xsrf: 'something' }, - sniffOnStart: false, - sniffOnConnectionFault: false, - hosts: ['http://localhost/elasticsearch'], - requestHeadersWhitelist: [], - log: customLogger, - }, - logger.get(), - 'custom-type' - ); - - expect(parsedConfig.log).toBe(customLogger); - }); -}); - -describe('#ssl', () => { - test('#verificationMode = none', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: {}, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - ssl: { verificationMode: 'none' }, - }, - logger.get(), - 'custom-type' - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": undefined, - "rejectUnauthorized": false, - }, - } - `); - }); - - test('#verificationMode = certificate', () => { - const clientConfig = parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: {}, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - ssl: { verificationMode: 'certificate' }, - }, - logger.get(), - 'custom-type' - ); - - // `checkServerIdentity` shouldn't check hostname when verificationMode is certificate. - expect( - clientConfig.ssl!.checkServerIdentity!('right.com', { subject: { CN: 'wrong.com' } } as any) - ).toBeUndefined(); - - expect(clientConfig).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": undefined, - "checkServerIdentity": [Function], - "rejectUnauthorized": true, - }, - } - `); - }); - - test('#verificationMode = full', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: {}, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - ssl: { verificationMode: 'full' }, - }, - logger.get(), - 'custom-type' - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": undefined, - "rejectUnauthorized": true, - }, - } - `); - }); - - test('#verificationMode is unknown', () => { - expect(() => - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: {}, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - ssl: { verificationMode: 'misspelled' as any }, - }, - logger.get(), - 'custom-type' - ) - ).toThrowErrorMatchingInlineSnapshot(`"Unknown ssl verificationMode: misspelled"`); - }); - - test('#ignoreCertAndKey = true', () => { - expect( - parseElasticsearchClientConfig( - { - apiVersion: 'v7.0.0', - customHeaders: {}, - sniffOnStart: true, - sniffOnConnectionFault: true, - hosts: ['https://es.local'], - requestHeadersWhitelist: [], - ssl: { - verificationMode: 'certificate', - certificateAuthorities: ['content-of-ca-path'], - certificate: 'content-of-certificate-path', - key: 'content-of-key-path', - keyPassphrase: 'key-pass', - alwaysPresentCertificate: true, - }, - }, - logger.get(), - 'custom-type', - { ignoreCertAndKey: true } - ) - ).toMatchInlineSnapshot(` - Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "x-elastic-product-origin": "kibana", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": Array [ - "content-of-ca-path", - ], - "checkServerIdentity": [Function], - "rejectUnauthorized": true, - }, - } - `); - }); -}); diff --git a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts deleted file mode 100644 index 3d81caefad4576..00000000000000 --- a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ConfigOptions } from 'elasticsearch'; -import { cloneDeep } from 'lodash'; -import { Duration } from 'moment'; -import { checkServerIdentity } from 'tls'; -import url from 'url'; -import { pick } from '@kbn/std'; -import { Logger } from '../../logging'; -import { ElasticsearchConfig } from '../elasticsearch_config'; -import { DEFAULT_HEADERS } from '../default_headers'; - -/** - * @privateRemarks Config that consumers can pass to the Elasticsearch JS client is complex and includes - * not only entries from standard `elasticsearch.*` yaml config, but also some Elasticsearch JS - * client specific options like `keepAlive` or `plugins` (that eventually will be deprecated). - * - * @deprecated - * @public - */ -export type LegacyElasticsearchClientConfig = Pick & - Pick< - ElasticsearchConfig, - | 'apiVersion' - | 'customHeaders' - | 'requestHeadersWhitelist' - | 'sniffOnStart' - | 'sniffOnConnectionFault' - | 'hosts' - | 'username' - | 'password' - | 'serviceAccountToken' - > & { - pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; - requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; - sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; - ssl?: Partial; - }; - -/** @internal */ -interface LegacyElasticsearchClientConfigOverrides { - /** - * If set to `true`, username and password from the config won't be used - * to access Elasticsearch API even if these are specified. - */ - auth?: boolean; - - /** - * If set to `true`, `ssl.key` and `ssl.certificate` provided through config won't - * be used to connect to Elasticsearch. - */ - ignoreCertAndKey?: boolean; -} - -// Original `ConfigOptions` defines `ssl: object` so we need something more specific. -/** @internal */ -type ExtendedConfigOptions = ConfigOptions & - Partial<{ - serviceAccountToken?: string; - ssl: Partial<{ - rejectUnauthorized: boolean; - checkServerIdentity: typeof checkServerIdentity; - ca: string[]; - cert: string; - key: string; - passphrase: string; - }>; - }>; - -/** @internal */ -export function parseElasticsearchClientConfig( - config: LegacyElasticsearchClientConfig, - log: Logger, - type: string, - { ignoreCertAndKey = false, auth = true }: LegacyElasticsearchClientConfigOverrides = {} -) { - const esClientConfig: ExtendedConfigOptions = { - keepAlive: true, - ...pick(config, [ - 'apiVersion', - 'sniffOnStart', - 'sniffOnConnectionFault', - 'keepAlive', - 'log', - 'plugins', - ]), - }; - - if (esClientConfig.log == null) { - esClientConfig.log = getLoggerClass(log, type); - } - - if (config.pingTimeout != null) { - esClientConfig.pingTimeout = getDurationAsMs(config.pingTimeout); - } - - if (config.requestTimeout != null) { - esClientConfig.requestTimeout = getDurationAsMs(config.requestTimeout); - } - - if (config.sniffInterval) { - esClientConfig.sniffInterval = getDurationAsMs(config.sniffInterval); - } - - const needsAuth = - auth !== false && ((config.username && config.password) || config.serviceAccountToken); - if (needsAuth) { - if (config.username) { - esClientConfig.httpAuth = `${config.username}:${config.password}`; - } else if (config.serviceAccountToken) { - esClientConfig.serviceAccountToken = config.serviceAccountToken; - } - } - - if (Array.isArray(config.hosts)) { - esClientConfig.hosts = config.hosts.map((nodeUrl: string) => { - const uri = url.parse(nodeUrl); - const httpsURI = uri.protocol === 'https:'; - const httpURI = uri.protocol === 'http:'; - - const host: Record = { - host: uri.hostname, - port: uri.port || (httpsURI && '443') || (httpURI && '80'), - protocol: uri.protocol, - path: uri.pathname, - query: uri.query, - headers: { - ...DEFAULT_HEADERS, - ...config.customHeaders, - }, - }; - - return host; - }); - } - - if (config.ssl === undefined) { - return cloneDeep(esClientConfig); - } - - esClientConfig.ssl = {}; - - const verificationMode = config.ssl.verificationMode; - switch (verificationMode) { - case 'none': - esClientConfig.ssl.rejectUnauthorized = false; - break; - case 'certificate': - esClientConfig.ssl.rejectUnauthorized = true; - - // by default, NodeJS is checking the server identify - esClientConfig.ssl.checkServerIdentity = () => undefined; - break; - case 'full': - esClientConfig.ssl.rejectUnauthorized = true; - break; - default: - throw new Error(`Unknown ssl verificationMode: ${verificationMode}`); - } - - esClientConfig.ssl.ca = config.ssl.certificateAuthorities; - - // Add client certificate and key if required by elasticsearch - if (!ignoreCertAndKey && config.ssl.certificate && config.ssl.key) { - esClientConfig.ssl.cert = config.ssl.certificate; - esClientConfig.ssl.key = config.ssl.key; - esClientConfig.ssl.passphrase = config.ssl.keyPassphrase; - } - - // Elasticsearch JS client mutates config object, so all properties that are - // usually passed by reference should be cloned to avoid any side effects. - return cloneDeep(esClientConfig); -} - -function getDurationAsMs(duration: number | Duration) { - if (typeof duration === 'number') { - return duration; - } - - return duration.asMilliseconds(); -} - -function getLoggerClass(log: Logger, type: string) { - const queryLogger = log.get('query', type); - - return class ElasticsearchClientLogging { - public error(err: string | Error) { - log.error(err); - } - - public warning(message: string) { - log.warn(message); - } - - public trace( - method: string, - options: { path: string }, - query: string, - _: unknown, - statusCode: string - ) { - queryLogger.debug(`${statusCode}\n${method} ${options.path}\n${query ? query.trim() : ''}`); - } - - // elasticsearch-js expects the following functions to exist - public info() { - // noop - } - - public debug() { - // noop - } - - public close() { - // noop - } - }; -} diff --git a/src/core/server/elasticsearch/legacy/errors.test.ts b/src/core/server/elasticsearch/legacy/errors.test.ts deleted file mode 100644 index 9973f70967a504..00000000000000 --- a/src/core/server/elasticsearch/legacy/errors.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Boom from '@hapi/boom'; - -import { LegacyElasticsearchErrorHelpers } from './errors'; - -describe('ElasticsearchErrorHelpers', () => { - describe('NotAuthorized error', () => { - describe('decorateNotAuthorizedError', () => { - it('returns original object', () => { - const error = new Error(); - expect(LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(error)).toBe(error); - }); - - it('makes the error identifiable as a NotAuthorized error', () => { - const error = new Error(); - expect(LegacyElasticsearchErrorHelpers.isNotAuthorizedError(error)).toBe(false); - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(error); - expect(LegacyElasticsearchErrorHelpers.isNotAuthorizedError(error)).toBe(true); - }); - - it('adds boom properties', () => { - const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); - expect(typeof error.output).toBe('object'); - expect(error.output.statusCode).toBe(401); - }); - - it('preserves boom properties of input', () => { - const error = Boom.notFound(); - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(error); - expect(error.output.statusCode).toBe(404); - }); - - describe('error.output', () => { - it('defaults to message of error', () => { - const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new Error('foobar') - ); - expect(error.output.payload).toHaveProperty('message', 'foobar'); - }); - it('prefixes message with passed reason', () => { - const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new Error('foobar'), - 'biz' - ); - expect(error.output.payload).toHaveProperty('message', 'biz: foobar'); - }); - it('sets statusCode to 401', () => { - const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new Error('foo') - ); - expect(error.output).toHaveProperty('statusCode', 401); - }); - }); - }); - }); -}); diff --git a/src/core/server/elasticsearch/legacy/errors.ts b/src/core/server/elasticsearch/legacy/errors.ts deleted file mode 100644 index 4111661bb83c4d..00000000000000 --- a/src/core/server/elasticsearch/legacy/errors.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Boom from '@hapi/boom'; -import { get } from 'lodash'; - -const code = Symbol('ElasticsearchError'); - -enum ErrorCode { - NOT_AUTHORIZED = 'Elasticsearch/notAuthorized', -} - -/** - * @deprecated. The new elasticsearch client doesn't wrap errors anymore. - * @removeBy 7.16 - * @public - * */ -export interface LegacyElasticsearchError extends Boom.Boom { - [code]?: string; -} - -function isElasticsearchError(error: any): error is LegacyElasticsearchError { - return Boolean(error && error[code]); -} - -function decorate( - error: Error, - errorCode: ErrorCode, - statusCode: number, - message?: string -): LegacyElasticsearchError { - if (isElasticsearchError(error)) { - return error; - } - - const boom = Boom.boomify(error, { - statusCode, - message, - // keep status and messages if Boom error object already has them - override: false, - }) as LegacyElasticsearchError; - - boom[code] = errorCode; - - return boom; -} - -/** - * Helpers for working with errors returned from the Elasticsearch service.Since the internal data of - * errors are subject to change, consumers of the Elasticsearch service should always use these helpers - * to classify errors instead of checking error internals such as `body.error.header[WWW-Authenticate]` - * @public - * - * @example - * Handle errors - * ```js - * try { - * await client.asScoped(request).callAsCurrentUser(...); - * } catch (err) { - * if (ElasticsearchErrorHelpers.isNotAuthorizedError(err)) { - * const authHeader = err.output.headers['WWW-Authenticate']; - * } - * ``` - */ -export class LegacyElasticsearchErrorHelpers { - public static isNotAuthorizedError(error: any): error is LegacyElasticsearchError { - return isElasticsearchError(error) && error[code] === ErrorCode.NOT_AUTHORIZED; - } - - public static decorateNotAuthorizedError(error: Error, reason?: string) { - const decoratedError = decorate(error, ErrorCode.NOT_AUTHORIZED, 401, reason); - const wwwAuthHeader = get(error, 'body.error.header[WWW-Authenticate]') as string; - - (decoratedError.output.headers as { [key: string]: string })['WWW-Authenticate'] = - wwwAuthHeader || 'Basic realm="Authorization Required"'; - - return decoratedError; - } -} diff --git a/src/core/server/elasticsearch/legacy/index.ts b/src/core/server/elasticsearch/legacy/index.ts deleted file mode 100644 index d98b7f16635a19..00000000000000 --- a/src/core/server/elasticsearch/legacy/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { LegacyClusterClient } from './cluster_client'; -export type { ILegacyClusterClient, ILegacyCustomClusterClient } from './cluster_client'; -export type { - ILegacyScopedClusterClient, - LegacyScopedClusterClient, -} from './scoped_cluster_client'; -export type { LegacyElasticsearchClientConfig } from './elasticsearch_client_config'; -export { LegacyElasticsearchErrorHelpers } from './errors'; -export type { LegacyElasticsearchError } from './errors'; -export * from './api_types'; diff --git a/src/core/server/elasticsearch/legacy/mocks.ts b/src/core/server/elasticsearch/legacy/mocks.ts deleted file mode 100644 index c8787af6bdb343..00000000000000 --- a/src/core/server/elasticsearch/legacy/mocks.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Client } from 'elasticsearch'; -import { ILegacyScopedClusterClient } from './scoped_cluster_client'; -import { ILegacyClusterClient, ILegacyCustomClusterClient } from './cluster_client'; - -const createScopedClusterClientMock = (): jest.Mocked => ({ - callAsInternalUser: jest.fn(), - callAsCurrentUser: jest.fn(), -}); - -const createCustomClusterClientMock = (): jest.Mocked => ({ - ...createClusterClientMock(), - close: jest.fn(), -}); - -function createClusterClientMock() { - const client: jest.Mocked = { - callAsInternalUser: jest.fn(), - asScoped: jest.fn(), - }; - client.asScoped.mockReturnValue(createScopedClusterClientMock()); - return client; -} - -const createElasticsearchClientMock = () => { - const mocked: jest.Mocked = { - cat: {} as any, - cluster: {} as any, - indices: {} as any, - ingest: {} as any, - nodes: {} as any, - snapshot: {} as any, - tasks: {} as any, - bulk: jest.fn(), - clearScroll: jest.fn(), - count: jest.fn(), - create: jest.fn(), - delete: jest.fn(), - deleteByQuery: jest.fn(), - deleteScript: jest.fn(), - deleteTemplate: jest.fn(), - exists: jest.fn(), - explain: jest.fn(), - fieldStats: jest.fn(), - get: jest.fn(), - getScript: jest.fn(), - getSource: jest.fn(), - getTemplate: jest.fn(), - index: jest.fn(), - info: jest.fn(), - mget: jest.fn(), - msearch: jest.fn(), - msearchTemplate: jest.fn(), - mtermvectors: jest.fn(), - ping: jest.fn(), - putScript: jest.fn(), - putTemplate: jest.fn(), - reindex: jest.fn(), - reindexRethrottle: jest.fn(), - renderSearchTemplate: jest.fn(), - scroll: jest.fn(), - search: jest.fn(), - searchShards: jest.fn(), - searchTemplate: jest.fn(), - suggest: jest.fn(), - termvectors: jest.fn(), - update: jest.fn(), - updateByQuery: jest.fn(), - close: jest.fn(), - }; - return mocked; -}; - -export const legacyClientMock = { - createScopedClusterClient: createScopedClusterClientMock, - createCustomClusterClient: createCustomClusterClientMock, - createClusterClient: createClusterClientMock, - createElasticsearchClient: createElasticsearchClientMock, -}; diff --git a/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts b/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts deleted file mode 100644 index 6607f3b694d82d..00000000000000 --- a/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { LegacyScopedClusterClient } from './scoped_cluster_client'; - -let internalAPICaller: jest.Mock; -let scopedAPICaller: jest.Mock; -let clusterClient: LegacyScopedClusterClient; -beforeEach(() => { - internalAPICaller = jest.fn(); - scopedAPICaller = jest.fn(); - clusterClient = new LegacyScopedClusterClient(internalAPICaller, scopedAPICaller, { one: '1' }); -}); - -afterEach(() => jest.clearAllMocks()); - -describe('#callAsInternalUser', () => { - test('properly forwards arguments to the API caller and results back from it', async () => { - const mockResponse = { data: 'response' }; - internalAPICaller.mockResolvedValue(mockResponse); - - await expect(clusterClient.callAsInternalUser('ping')).resolves.toBe(mockResponse); - expect(internalAPICaller).toHaveBeenCalledTimes(1); - expect(internalAPICaller).toHaveBeenCalledWith('ping', {}, undefined); - internalAPICaller.mockClear(); - - await expect( - clusterClient.callAsInternalUser('security.authenticate', { some: 'some' }) - ).resolves.toBe(mockResponse); - expect(internalAPICaller).toHaveBeenCalledTimes(1); - expect(internalAPICaller).toHaveBeenCalledWith( - 'security.authenticate', - { some: 'some' }, - undefined - ); - internalAPICaller.mockClear(); - - await expect( - clusterClient.callAsInternalUser('ping', undefined, { wrap401Errors: true }) - ).resolves.toBe(mockResponse); - expect(internalAPICaller).toHaveBeenCalledTimes(1); - expect(internalAPICaller).toHaveBeenCalledWith('ping', {}, { wrap401Errors: true }); - internalAPICaller.mockClear(); - - await expect( - clusterClient.callAsInternalUser( - 'security.authenticate', - { some: 'some' }, - { wrap401Errors: true } - ) - ).resolves.toBe(mockResponse); - expect(internalAPICaller).toHaveBeenCalledTimes(1); - expect(internalAPICaller).toHaveBeenCalledWith( - 'security.authenticate', - { some: 'some' }, - { wrap401Errors: true } - ); - - expect(scopedAPICaller).not.toHaveBeenCalled(); - }); - - test('properly forwards errors returned by the API caller', async () => { - const mockErrorResponse = new Error('some-error'); - internalAPICaller.mockRejectedValue(mockErrorResponse); - - await expect(clusterClient.callAsInternalUser('ping')).rejects.toBe(mockErrorResponse); - - expect(scopedAPICaller).not.toHaveBeenCalled(); - }); -}); - -describe('#callAsCurrentUser', () => { - test('properly forwards arguments to the API caller and results back from it', async () => { - const mockResponse = { data: 'response' }; - scopedAPICaller.mockResolvedValue(mockResponse); - - await expect(clusterClient.callAsCurrentUser('ping')).resolves.toBe(mockResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith('ping', { headers: { one: '1' } }, undefined); - scopedAPICaller.mockClear(); - - await expect( - clusterClient.callAsCurrentUser('security.authenticate', { some: 'some' }) - ).resolves.toBe(mockResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith( - 'security.authenticate', - { some: 'some', headers: { one: '1' } }, - undefined - ); - scopedAPICaller.mockClear(); - - await expect( - clusterClient.callAsCurrentUser('security.authenticate', { some: 'some' }) - ).resolves.toBe(mockResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith( - 'security.authenticate', - { some: 'some', headers: { one: '1' } }, - undefined - ); - scopedAPICaller.mockClear(); - - await expect( - clusterClient.callAsCurrentUser('ping', undefined, { wrap401Errors: true }) - ).resolves.toBe(mockResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith( - 'ping', - { headers: { one: '1' } }, - { wrap401Errors: true } - ); - scopedAPICaller.mockClear(); - - await expect( - clusterClient.callAsCurrentUser( - 'security.authenticate', - { some: 'some' }, - { wrap401Errors: true } - ) - ).resolves.toBe(mockResponse); - - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith( - 'security.authenticate', - { some: 'some', headers: { one: '1' } }, - { wrap401Errors: true } - ); - - expect(internalAPICaller).not.toHaveBeenCalled(); - }); - - test('callAsCurrentUser allows passing additional headers', async () => { - const mockResponse = { data: 'response' }; - scopedAPICaller.mockResolvedValue(mockResponse); - await expect( - clusterClient.callAsCurrentUser('security.authenticate', { - some: 'some', - headers: { additionalHeader: 'Oh Yes!' }, - }) - ).resolves.toBe(mockResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith( - 'security.authenticate', - { some: 'some', headers: { one: '1', additionalHeader: 'Oh Yes!' } }, - undefined - ); - }); - - test('callAsCurrentUser cannot override default headers', async () => { - const expectedErrorResponse = new Error('Cannot override default header one.'); - const withHeaderOverride = async () => - clusterClient.callAsCurrentUser('security.authenticate', { headers: { one: 'OVERRIDE' } }); - await expect(withHeaderOverride()).rejects.toThrowError(expectedErrorResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(0); - }); - - test('properly forwards errors returned by the API caller', async () => { - const mockErrorResponse = new Error('some-error'); - scopedAPICaller.mockRejectedValue(mockErrorResponse); - - await expect(clusterClient.callAsCurrentUser('ping')).rejects.toBe(mockErrorResponse); - - expect(internalAPICaller).not.toHaveBeenCalled(); - }); - - test('does not attach headers to the client params if they are not available', async () => { - const mockResponse = { data: 'response' }; - scopedAPICaller.mockResolvedValue(mockResponse); - - const clusterClientWithoutHeaders = new LegacyScopedClusterClient( - internalAPICaller, - scopedAPICaller - ); - - await expect(clusterClientWithoutHeaders.callAsCurrentUser('ping')).resolves.toBe(mockResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith('ping', {}, undefined); - - scopedAPICaller.mockClear(); - await expect( - clusterClientWithoutHeaders.callAsCurrentUser('security.authenticate', { some: 'some' }) - ).resolves.toBe(mockResponse); - expect(scopedAPICaller).toHaveBeenCalledTimes(1); - expect(scopedAPICaller).toHaveBeenCalledWith( - 'security.authenticate', - { some: 'some' }, - undefined - ); - - expect(internalAPICaller).not.toHaveBeenCalled(); - }); -}); diff --git a/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts b/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts deleted file mode 100644 index d58519dd25a7f6..00000000000000 --- a/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { intersection, isObject } from 'lodash'; -import { Headers } from '../../http/router'; -import { LegacyAPICaller, LegacyCallAPIOptions } from './api_types'; - -/** - * Serves the same purpose as "normal" `ClusterClient` but exposes additional - * `callAsCurrentUser` method that doesn't use credentials of the Kibana internal - * user (as `callAsInternalUser` does) to request Elasticsearch API, but rather - * passes HTTP headers extracted from the current user request to the API. - * - * See {@link LegacyScopedClusterClient}. - * - * @deprecated Use {@link IScopedClusterClient}. - * @removeBy 7.16 - * @public - */ -export type ILegacyScopedClusterClient = Pick< - LegacyScopedClusterClient, - 'callAsCurrentUser' | 'callAsInternalUser' ->; - -/** - * {@inheritDoc IScopedClusterClient} - * @deprecated Use {@link IScopedClusterClient | scoped cluster client}. - * @removeBy 7.16 - * @public - */ -export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { - constructor( - private readonly internalAPICaller: LegacyAPICaller, - private readonly scopedAPICaller: LegacyAPICaller, - private readonly headers?: Headers - ) { - this.callAsCurrentUser = this.callAsCurrentUser.bind(this); - this.callAsInternalUser = this.callAsInternalUser.bind(this); - } - - /** - * Calls specified `endpoint` with provided `clientParams` on behalf of the - * Kibana internal user. - * See {@link LegacyAPICaller}. - * @deprecated Use {@link IScopedClusterClient.asInternalUser}. - * @removeBy 7.16 - * - * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. - * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. - * @param options - Options that affect the way we call the API and process the result. - */ - public callAsInternalUser( - endpoint: string, - clientParams: Record = {}, - options?: LegacyCallAPIOptions - ) { - return this.internalAPICaller(endpoint, clientParams, options); - } - - /** - * Calls specified `endpoint` with provided `clientParams` on behalf of the - * user initiated request to the Kibana server (via HTTP request headers). - * See {@link LegacyAPICaller}. - * @deprecated Use {@link IScopedClusterClient.asCurrentUser}. - * @removeBy 7.16 - * - * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. - * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. - * @param options - Options that affect the way we call the API and process the result. - */ - public callAsCurrentUser( - endpoint: string, - clientParams: Record = {}, - options?: LegacyCallAPIOptions - ) { - const defaultHeaders = this.headers; - if (defaultHeaders !== undefined) { - const customHeaders: any = clientParams.headers; - if (isObject(customHeaders)) { - const duplicates = intersection(Object.keys(defaultHeaders), Object.keys(customHeaders)); - duplicates.forEach((duplicate) => { - if (defaultHeaders[duplicate] !== (customHeaders as any)[duplicate]) { - throw Error(`Cannot override default header ${duplicate}.`); - } - }); - } - - clientParams.headers = Object.assign({}, clientParams.headers, this.headers); - } - - return this.scopedAPICaller(endpoint, clientParams, options); - } -} diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index 375c7015b16d79..89a7d752f79121 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -10,11 +10,6 @@ import { Observable } from 'rxjs'; import { Headers } from '../http/router'; import { LegacyRequest, KibanaRequest } from '../http'; import { ElasticsearchConfig } from './elasticsearch_config'; -import { - LegacyElasticsearchClientConfig, - ILegacyClusterClient, - ILegacyCustomClusterClient, -} from './legacy'; import { IClusterClient, ICustomClusterClient, ElasticsearchClientConfig } from './client'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; import { ServiceStatus } from '../status'; @@ -71,46 +66,6 @@ export interface ElasticsearchServiceSetup { * @deprecated this will be removed in a later version. */ readonly config$: Observable; - /** - * @deprecated - * @removeBy 7.16 - * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.createClient} instead. - * - * Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}. - * - * @param type Unique identifier of the client - * @param clientConfig A config consists of Elasticsearch JS client options and - * valid sub-set of Elasticsearch service config. - * We fill all the missing properties in the `clientConfig` using the default - * Elasticsearch config so that we don't depend on default values set and - * controlled by underlying Elasticsearch JS client. - * We don't run validation against the passed config and expect it to be valid. - * - * @example - * ```js - * const client = elasticsearch.createCluster('my-app-name', config); - * const data = await client.callAsInternalUser(); - * ``` - */ - readonly createClient: ( - type: string, - clientConfig?: Partial - ) => ILegacyCustomClusterClient; - - /** - * @removeBy 7.16 - * @deprecated - * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.client} instead. - * - * All Elasticsearch config value changes are processed under the hood. - * See {@link ILegacyClusterClient}. - * - * @example - * ```js - * const client = core.elasticsearch.legacy.client; - * ``` - */ - readonly client: ILegacyClusterClient; }; } @@ -170,43 +125,6 @@ export interface ElasticsearchServiceStart { * @deprecated this will be removed in a later version. */ readonly config$: Observable; - /** - * Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}. - * - * @deprecated - * @removeBy 7.16 - * - * @param type Unique identifier of the client - * @param clientConfig A config consists of Elasticsearch JS client options and - * valid sub-set of Elasticsearch service config. - * We fill all the missing properties in the `clientConfig` using the default - * Elasticsearch config so that we don't depend on default values set and - * controlled by underlying Elasticsearch JS client. - * We don't run validation against the passed config and expect it to be valid. - * - * @example - * ```js - * const client = elasticsearch.legacy.createClient('my-app-name', config); - * const data = await client.callAsInternalUser(); - * ``` - */ - readonly createClient: ( - type: string, - clientConfig?: Partial - ) => ILegacyCustomClusterClient; - - /** - * A pre-configured {@link ILegacyClusterClient | legacy Elasticsearch client}. - * - * @deprecated - * @removeBy 7.16 - * - * @example - * ```js - * const client = core.elasticsearch.legacy.client; - * ``` - */ - readonly client: ILegacyClusterClient; }; } diff --git a/src/core/server/http/integration_tests/core_service.test.mocks.ts b/src/core/server/http/integration_tests/core_service.test.mocks.ts index 2e2d8a4b5f03a7..9aa801c1e7759c 100644 --- a/src/core/server/http/integration_tests/core_service.test.mocks.ts +++ b/src/core/server/http/integration_tests/core_service.test.mocks.ts @@ -6,27 +6,6 @@ * Side Public License, v 1. */ -import { elasticsearchServiceMock } from '../../elasticsearch/elasticsearch_service.mock'; - -export const MockLegacyScopedClusterClient = jest.fn(); -export const legacyClusterClientInstanceMock = elasticsearchServiceMock.createLegacyScopedClusterClient(); -jest.doMock('../../elasticsearch/legacy/scoped_cluster_client', () => ({ - LegacyScopedClusterClient: MockLegacyScopedClusterClient.mockImplementation( - () => legacyClusterClientInstanceMock - ), -})); - -jest.doMock('elasticsearch', () => { - const realES = jest.requireActual('elasticsearch'); - return { - ...realES, - // eslint-disable-next-line object-shorthand - Client: function () { - return elasticsearchServiceMock.createLegacyElasticsearchClient(); - }, - }; -}); - export const MockElasticsearchClient = jest.fn(); jest.doMock('@elastic/elasticsearch', () => { const real = jest.requireActual('@elastic/elasticsearch'); diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index e497f254e06329..0c2d6896573bdb 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -6,15 +6,7 @@ * Side Public License, v 1. */ -import { - MockLegacyScopedClusterClient, - MockElasticsearchClient, - legacyClusterClientInstanceMock, -} from './core_service.test.mocks'; - -import { errors as esErrors } from 'elasticsearch'; -import { LegacyElasticsearchErrorHelpers } from '../../elasticsearch/legacy'; - +import { MockElasticsearchClient } from './core_service.test.mocks'; import { elasticsearchClientMock } from '../../elasticsearch/client/mocks'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import * as kbnTestServer from '../../../test_helpers/kbn_server'; @@ -186,99 +178,6 @@ describe('http service', () => { }); }); - describe('legacy elasticsearch client', () => { - let root: ReturnType; - beforeEach(async () => { - root = kbnTestServer.createRoot({ plugins: { initialize: false } }); - await root.preboot(); - }, 30000); - - afterEach(async () => { - MockLegacyScopedClusterClient.mockClear(); - await root.shutdown(); - }); - - it('rewrites authorization header via authHeaders to make a request to Elasticsearch', async () => { - const authHeaders = { authorization: 'Basic: user:password' }; - const { http } = await root.setup(); - const { registerAuth, createRouter } = http; - - registerAuth((req, res, toolkit) => toolkit.authenticated({ requestHeaders: authHeaders })); - - const router = createRouter('/new-platform'); - router.get({ path: '/', validate: false }, async (context, req, res) => { - // it forces client initialization since the core creates them lazily. - await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); - return res.ok(); - }); - - await root.start(); - - await kbnTestServer.request.get(root, '/new-platform/').expect(200); - - // client contains authHeaders for BWC with legacy platform. - const [client] = MockLegacyScopedClusterClient.mock.calls; - const [, , clientHeaders] = client; - expect(clientHeaders).toEqual({ - ...authHeaders, - 'x-opaque-id': expect.any(String), - }); - }); - - it('passes request authorization header to Elasticsearch if registerAuth was not set', async () => { - const authorizationHeader = 'Basic: username:password'; - const { http } = await root.setup(); - const { createRouter } = http; - - const router = createRouter('/new-platform'); - router.get({ path: '/', validate: false }, async (context, req, res) => { - // it forces client initialization since the core creates them lazily. - await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); - return res.ok(); - }); - - await root.start(); - - await kbnTestServer.request - .get(root, '/new-platform/') - .set('Authorization', authorizationHeader) - .expect(200); - - const [client] = MockLegacyScopedClusterClient.mock.calls; - const [, , clientHeaders] = client; - expect(clientHeaders).toEqual({ - authorization: authorizationHeader, - 'x-opaque-id': expect.any(String), - }); - }); - - it('forwards 401 errors returned from elasticsearch', async () => { - const { http } = await root.setup(); - const { createRouter } = http; - - const authenticationError = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (esErrors.AuthenticationException as any)('Authentication Exception', { - body: { error: { header: { 'WWW-Authenticate': 'authenticate header' } } }, - statusCode: 401, - }) - ); - - legacyClusterClientInstanceMock.callAsCurrentUser.mockRejectedValue(authenticationError); - - const router = createRouter('/new-platform'); - router.get({ path: '/', validate: false }, async (context, req, res) => { - await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); - return res.ok(); - }); - - await root.start(); - - const response = await kbnTestServer.request.get(root, '/new-platform/').expect(401); - - expect(response.header['www-authenticate']).toEqual('authenticate header'); - }); - }); - describe('elasticsearch client', () => { let root: ReturnType; diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index 1e7297ddcba3b2..75fcc1bb0e0832 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -11,7 +11,6 @@ import Boom from '@hapi/boom'; import { isConfigSchema } from '@kbn/config-schema'; import { Logger } from '../../logging'; -import { LegacyElasticsearchErrorHelpers } from '../../elasticsearch/legacy/errors'; import { isUnauthorizedError as isElasticsearchUnauthorizedError, UnauthorizedError as EsNotAuthorizedError, @@ -280,10 +279,6 @@ export class Router { // (undocumented) @@ -862,38 +726,6 @@ export interface DeleteDocumentResponse { _version: number; } -// @public @deprecated (undocumented) -export interface DeprecationAPIClientParams extends GenericParams { - // (undocumented) - method: 'GET'; - // (undocumented) - path: '/_migration/deprecations'; -} - -// @public @deprecated (undocumented) -export interface DeprecationAPIResponse { - // (undocumented) - cluster_settings: DeprecationInfo[]; - // (undocumented) - index_settings: IndexSettingsDeprecationInfo; - // (undocumented) - ml_settings: DeprecationInfo[]; - // (undocumented) - node_settings: DeprecationInfo[]; -} - -// @public @deprecated (undocumented) -export interface DeprecationInfo { - // (undocumented) - details?: string; - // (undocumented) - level: MIGRATION_DEPRECATION_LEVEL; - // (undocumented) - message: string; - // (undocumented) - url: string; -} - // @public export interface DeprecationsClient { // Warning: (ae-forgotten-export) The symbol "DomainDeprecationDetails" needs to be exported by the entry point index.d.ts @@ -1019,8 +851,6 @@ export interface ElasticsearchServiceSetup { // @deprecated (undocumented) legacy: { readonly config$: Observable; - readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; - readonly client: ILegacyClusterClient; }; } @@ -1031,8 +861,6 @@ export interface ElasticsearchServiceStart { // @deprecated (undocumented) legacy: { readonly config$: Observable; - readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; - readonly client: ILegacyClusterClient; }; } @@ -1286,21 +1114,6 @@ export interface IKibanaSocket { }): Promise; } -// @public @deprecated -export type ILegacyClusterClient = Pick; - -// @public @deprecated -export type ILegacyCustomClusterClient = Pick; - -// @public @deprecated -export type ILegacyScopedClusterClient = Pick; - -// @public @deprecated (undocumented) -export interface IndexSettingsDeprecationInfo { - // (undocumented) - [indexName: string]: DeprecationInfo[]; -} - // @public (undocumented) export interface IRenderOptions { includeUserSettings?: boolean; @@ -1450,300 +1263,10 @@ export const kibanaResponseFactory: { // @public export type KnownHeaders = KnownKeys; -// @public @deprecated (undocumented) -export interface LegacyAPICaller { - // (undocumented) - (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'clearScroll', params: ClearScrollParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'count', params: CountParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'create', params: CreateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'delete', params: DeleteDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteScript', params: DeleteScriptParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'exists', params: ExistsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'explain', params: ExplainParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'fieldStats', params: FieldStatsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'get', params: GetParams, options?: LegacyCallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'getScript', params: GetScriptParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getSource', params: GetSourceParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getTemplate', params: GetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'index', params: IndexDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'info', params: InfoParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'mget', params: MGetParams, options?: LegacyCallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearch', params: MSearchParams, options?: LegacyCallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: LegacyCallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ping', params: PingParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putScript', params: PutScriptParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putTemplate', params: PutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindex', params: ReindexParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'scroll', params: ScrollParams, options?: LegacyCallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'search', params: SearchParams, options?: LegacyCallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'searchShards', params: SearchShardsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'suggest', params: SuggestParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'termvectors', params: TermvectorsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'update', params: UpdateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.aliases', params: CatAliasesParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.allocation', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.count', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.health', params: CatHealthParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.help', params: CatHelpParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.indices', params: CatIndicesParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.master', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodes', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.plugins', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.repositories', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.segments', params: CatSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.shards', params: CatShardsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.tasks', params: CatTasksParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.health', params: ClusterHealthParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.state', params: ClusterStateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.close', params: IndicesCloseParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.create', params: IndicesCreateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.exists', params: IndicesExistsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flush', params: IndicesFlushParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.get', params: IndicesGetParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.open', params: IndicesOpenParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.stats', params: IndicesStatsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.info', params: NodesInfoParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.stats', params: NodesStatsParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.get', params: TasksGetParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.list', params: TasksListParams, options?: LegacyCallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: LegacyCallAPIOptions): Promise; - // (undocumented) - (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: LegacyCallAPIOptions): Promise; - // (undocumented) - (endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; -} - -// @public @deprecated -export interface LegacyCallAPIOptions { - signal?: AbortSignal; - wrap401Errors?: boolean; -} - -// @public @deprecated -export class LegacyClusterClient implements ILegacyClusterClient { - constructor(config: LegacyElasticsearchClientConfig, log: Logger, type: string, getAuthHeaders?: GetAuthHeaders); - asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient; - // @deprecated - callAsInternalUser: LegacyAPICaller; - close(): void; - } - -// @public @deprecated (undocumented) -export type LegacyElasticsearchClientConfig = Pick & Pick & { - pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; - requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; - sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; - ssl?: Partial; -}; - -// @public -export interface LegacyElasticsearchError extends Boom.Boom { - // (undocumented) - [code_2]?: string; -} - -// @public -export class LegacyElasticsearchErrorHelpers { - // (undocumented) - static decorateNotAuthorizedError(error: Error, reason?: string): LegacyElasticsearchError; - // (undocumented) - static isNotAuthorizedError(error: any): error is LegacyElasticsearchError; -} - // @public @deprecated (undocumented) export interface LegacyRequest extends Request { } -// @public @deprecated -export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { - constructor(internalAPICaller: LegacyAPICaller, scopedAPICaller: LegacyAPICaller, headers?: Headers | undefined); - // @deprecated - callAsCurrentUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; - // @deprecated - callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; - } - // Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts // // @public @@ -1793,12 +1316,6 @@ export interface MetricsServiceSetup { // @public export type MetricsServiceStart = MetricsServiceSetup; -// @public @deprecated (undocumented) -export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; - -// @public @deprecated (undocumented) -export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; - // @public export type MutatingOperationRefreshSetting = boolean | 'wait_for'; @@ -2090,6 +1607,8 @@ export interface RegisterDeprecationsConfig { // @public export type RequestHandler

= (context: Context, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "LegacyScopedClusterClient" +// // @public export interface RequestHandlerContext { // (undocumented) @@ -2103,9 +1622,6 @@ export interface RequestHandlerContext { }; elasticsearch: { client: IScopedClusterClient; - legacy: { - client: ILegacyScopedClusterClient; - }; }; uiSettings: { client: IUiSettingsClient; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index f684586917fe7e..7eafad71f4f959 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -693,7 +693,7 @@ export class Plugin implements Plugin_2, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; diff --git a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts index 08087356a6cd2e..6701ff97cca803 100644 --- a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts @@ -22,8 +22,10 @@ export class CorePluginAPlugin implements Plugin { 'pluginA', (context) => { return { - ping: () => - context.core.elasticsearch.legacy.client.callAsInternalUser('ping') as Promise, + ping: async () => { + const { body } = await context.core.elasticsearch.client.asInternalUser.ping(); + return String(body); + }, }; } ); diff --git a/x-pack/plugins/global_search/server/services/context.mock.ts b/x-pack/plugins/global_search/server/services/context.mock.ts index c5cf3ef49b9dfd..c7f43ad5b8ff02 100644 --- a/x-pack/plugins/global_search/server/services/context.mock.ts +++ b/x-pack/plugins/global_search/server/services/context.mock.ts @@ -10,7 +10,6 @@ import { Capabilities } from 'src/core/server'; import { savedObjectsTypeRegistryMock, savedObjectsClientMock, - elasticsearchServiceMock, uiSettingsServiceMock, capabilitiesServiceMock, } from '../../../../../src/core/server/mocks'; @@ -22,11 +21,6 @@ const createContextMock = (capabilities: Partial = {}) => { client: savedObjectsClientMock.create(), typeRegistry: savedObjectsTypeRegistryMock.create(), }, - elasticsearch: { - legacy: { - client: elasticsearchServiceMock.createLegacyScopedClusterClient(), - }, - }, uiSettings: { client: uiSettingsServiceMock.createClient(), }, diff --git a/x-pack/plugins/global_search/server/services/context.test.ts b/x-pack/plugins/global_search/server/services/context.test.ts index 4fefc009fe1ecf..580083efbb3036 100644 --- a/x-pack/plugins/global_search/server/services/context.test.ts +++ b/x-pack/plugins/global_search/server/services/context.test.ts @@ -21,9 +21,6 @@ describe('getContextFactory', () => { expect(coreStart.savedObjects.getTypeRegistry).toHaveBeenCalledTimes(1); - expect(coreStart.elasticsearch.legacy.client.asScoped).toHaveBeenCalledTimes(1); - expect(coreStart.elasticsearch.legacy.client.asScoped).toHaveBeenCalledWith(request); - const soClient = coreStart.savedObjects.getScopedClient.mock.results[0].value; expect(coreStart.uiSettings.asScopedToClient).toHaveBeenCalledTimes(1); expect(coreStart.uiSettings.asScopedToClient).toHaveBeenCalledWith(soClient); @@ -34,7 +31,6 @@ describe('getContextFactory', () => { expect(context).toEqual({ core: { savedObjects: expect.any(Object), - elasticsearch: expect.any(Object), uiSettings: expect.any(Object), capabilities: expect.any(Object), }, diff --git a/x-pack/plugins/global_search/server/services/context.ts b/x-pack/plugins/global_search/server/services/context.ts index f42a8417b3c258..454b0324ab3c13 100644 --- a/x-pack/plugins/global_search/server/services/context.ts +++ b/x-pack/plugins/global_search/server/services/context.ts @@ -24,11 +24,6 @@ export const getContextFactory = (coreStart: CoreStart) => ( client: soClient, typeRegistry: coreStart.savedObjects.getTypeRegistry(), }, - elasticsearch: { - legacy: { - client: coreStart.elasticsearch.legacy.client.asScoped(request), - }, - }, uiSettings: { client: coreStart.uiSettings.asScopedToClient(soClient), }, diff --git a/x-pack/plugins/global_search/server/types.ts b/x-pack/plugins/global_search/server/types.ts index 1732df69f997f0..363f837f4bd2e1 100644 --- a/x-pack/plugins/global_search/server/types.ts +++ b/x-pack/plugins/global_search/server/types.ts @@ -8,7 +8,6 @@ import { Observable } from 'rxjs'; import type { ISavedObjectTypeRegistry, - ILegacyScopedClusterClient, IUiSettingsClient, SavedObjectsClientContract, Capabilities, @@ -68,11 +67,6 @@ export interface GlobalSearchProviderContext { client: SavedObjectsClientContract; typeRegistry: ISavedObjectTypeRegistry; }; - elasticsearch: { - legacy: { - client: ILegacyScopedClusterClient; - }; - }; uiSettings: { client: IUiSettingsClient; }; diff --git a/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts b/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts index 6d47882ca86c41..0555b3320ad911 100644 --- a/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts @@ -11,7 +11,6 @@ import { RacRequestHandlerContext } from '../../types'; const createMockClients = () => ({ rac: alertsClientMock.create(), - clusterClient: elasticsearchServiceMock.createLegacyScopedClusterClient(), newClusterClient: elasticsearchServiceMock.createScopedClusterClient(), savedObjectsClient: savedObjectsClientMock.create(), }); @@ -27,7 +26,6 @@ const createRequestContextMock = ( elasticsearch: { ...coreContext.elasticsearch, client: clients.newClusterClient, - legacy: { ...coreContext.elasticsearch.legacy, client: clients.clusterClient }, }, savedObjects: { client: clients.savedObjectsClient }, }, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts index df2e9df4ba1890..5338ab0c401e52 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts @@ -22,7 +22,6 @@ import { GeoContainmentInstanceState, GeoContainmentParams, } from '../alert_type'; -import { SearchResponse } from 'elasticsearch'; const alertInstanceFactory = (contextKeys: unknown[], testAlertActionArr: unknown[]) => ( instanceId: string @@ -53,7 +52,7 @@ describe('geo_containment', () => { it('should correctly transform expected results', async () => { const transformedResults = transformResults( // @ts-ignore - (sampleAggsJsonResponse.body as unknown) as SearchResponse, + sampleAggsJsonResponse.body, dateField, geoField ); @@ -113,7 +112,7 @@ describe('geo_containment', () => { it('should correctly transform expected results if fields are nested', async () => { const transformedResults = transformResults( // @ts-ignore - (sampleAggsJsonResponseWithNesting.body as unknown) as SearchResponse, + sampleAggsJsonResponseWithNesting.body, nestedDateField, nestedGeoField ); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts index 51a530317d3785..9d0dffd2ebb8a8 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager_fixture/server/plugin.ts @@ -90,7 +90,7 @@ export class SampleTaskManagerFixturePlugin req: KibanaRequest, res: KibanaResponseFactory ): Promise> { - await core.elasticsearch.legacy.client.callAsInternalUser('indices.refresh', { + await context.core.elasticsearch.client.asInternalUser.indices.refresh({ index: '.kibana_task_manager', }); return res.ok({ body: {} }); diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts index 5d0d72ae94ac35..d761bc50ce0803 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts @@ -12,7 +12,7 @@ import { KibanaResponseFactory, IKibanaResponse, IRouter, - CoreSetup, + IScopedClusterClient, } from 'src/core/server'; import { EventEmitter } from 'events'; import { TaskManagerStartContract } from '../../../../../plugins/task_manager/server'; @@ -36,12 +36,11 @@ const taskManagerQuery = { export function initRoutes( router: IRouter, - core: CoreSetup, taskManagerStart: Promise, taskTestingEvents: EventEmitter ) { - async function ensureIndexIsRefreshed() { - return await core.elasticsearch.legacy.client.callAsInternalUser('indices.refresh', { + async function ensureIndexIsRefreshed(client: IScopedClusterClient) { + return await client.asInternalUser.indices.refresh({ index: '.kibana_task_manager', }); } @@ -250,7 +249,7 @@ export function initRoutes( res: KibanaResponseFactory ): Promise> { try { - await ensureIndexIsRefreshed(); + await ensureIndexIsRefreshed(context.core.elasticsearch.client); const taskManager = await taskManagerStart; return res.ok({ body: await taskManager.get(req.params.taskId) }); } catch ({ isBoom, output, message }) { @@ -269,7 +268,7 @@ export function initRoutes( req: KibanaRequest, res: KibanaResponseFactory ): Promise> { - await ensureIndexIsRefreshed(); + await ensureIndexIsRefreshed(context.core.elasticsearch.client); return res.ok({ body: {} }); } ); @@ -285,7 +284,7 @@ export function initRoutes( res: KibanaResponseFactory ): Promise> { try { - await ensureIndexIsRefreshed(); + await ensureIndexIsRefreshed(context.core.elasticsearch.client); let tasksFound = 0; const taskManager = await taskManagerStart; do { diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts index e4770f79b360d6..30c3601277d2d7 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts @@ -69,7 +69,8 @@ export class SampleTaskManagerFixturePlugin } } - await core.elasticsearch.legacy.client.callAsInternalUser('index', { + const [{ elasticsearch }] = await core.getStartServices(); + await elasticsearch.client.asInternalUser.index({ index: '.kibana_task_manager_test_result', body: { type: 'task', @@ -270,7 +271,7 @@ export class SampleTaskManagerFixturePlugin return context; }, }); - initRoutes(core.http.createRouter(), core, this.taskManagerStart, taskTestingEvents); + initRoutes(core.http.createRouter(), this.taskManagerStart, taskTestingEvents); } public start(core: CoreStart, { taskManager }: SampleTaskManagerFixtureStartDeps) { diff --git a/yarn.lock b/yarn.lock index e49d7590c21aad..8a71ff74738e1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12363,7 +12363,7 @@ elastic-apm-node@^3.16.0: traverse "^0.6.6" unicode-byte-truncate "^1.0.0" -elasticsearch@^16.4.0, elasticsearch@^16.7.0: +elasticsearch@^16.4.0: version "16.7.0" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-16.7.0.tgz#9055e3f586934d8de5fd407b04050e9d54173333" integrity sha512-du+//TbjCFEkaG0jNcAC95Fp4B6/X5shnCRIXALFL+M4U5iT3YL5ZVUPNf1NgR7dy/sc8Dvw2Ob6IUJKB7FrCw== From fd676afb9c4a8169da185d1a537fc98bd3136bd3 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 18 Aug 2021 19:21:26 +0200 Subject: [PATCH 14/36] text improvement (#109111) --- src/plugins/data/public/ui/saved_query_form/save_query_form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx index 077b9ac47286d9..d0221658f3e08b 100644 --- a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx +++ b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx @@ -143,7 +143,7 @@ export function SaveQueryForm({ })} helpText={i18n.translate('data.search.searchBar.savedQueryNameHelpText', { defaultMessage: - 'Name is required. Name cannot contain leading or trailing whitespace. Name must be unique.', + 'Name is required, it cannot contain leading or trailing whitespace and must be unique.', })} isInvalid={hasErrors} > From 2e5eb5ce6661498ccc648cc3a8956411d4d617aa Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 18 Aug 2021 19:26:44 +0200 Subject: [PATCH 15/36] [APM] Prevent infinite loop when updating time range (#109043) Closes #108983. --- .../apm/public/hooks/use_time_range.test.ts | 39 +++++++------------ .../apm/public/hooks/use_time_range.ts | 17 +++----- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/apm/public/hooks/use_time_range.test.ts b/x-pack/plugins/apm/public/hooks/use_time_range.test.ts index dbdd7de171650e..9ce104939d4ff0 100644 --- a/x-pack/plugins/apm/public/hooks/use_time_range.test.ts +++ b/x-pack/plugins/apm/public/hooks/use_time_range.test.ts @@ -4,11 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { - act, - renderHook, - RenderHookResult, -} from '@testing-library/react-hooks'; +import { renderHook, RenderHookResult } from '@testing-library/react-hooks'; import { useTimeRange } from './use_time_range'; describe('useTimeRange', () => { @@ -29,40 +25,33 @@ describe('useTimeRange', () => { ); }); - afterEach(() => {}); - it('returns the parsed range on first render', () => { expect(hook.result.current.start).toEqual('2021-01-01T11:45:00.000Z'); expect(hook.result.current.end).toEqual('2021-01-01T12:00:00.000Z'); }); it('only changes the parsed range when rangeFrom/rangeTo change', () => { - Date.now = jest.fn(() => new Date(Date.UTC(2021, 0, 1, 13)).valueOf()); - - hook.rerender({ rangeFrom: 'now-15m', rangeTo: 'now' }); - - expect(hook.result.current.start).toEqual('2021-01-01T11:45:00.000Z'); - expect(hook.result.current.end).toEqual('2021-01-01T12:00:00.000Z'); - hook.rerender({ rangeFrom: 'now-30m', rangeTo: 'now' }); - expect(hook.result.current.start).toEqual('2021-01-01T12:30:00.000Z'); - expect(hook.result.current.end).toEqual('2021-01-01T13:00:00.000Z'); - }); + expect(hook.result.current.start).toEqual('2021-01-01T11:30:00.000Z'); + expect(hook.result.current.end).toEqual('2021-01-01T12:00:00.000Z'); - it('updates when refreshTimeRange is called', async () => { Date.now = jest.fn(() => new Date(Date.UTC(2021, 0, 1, 13)).valueOf()); - hook.rerender({ rangeFrom: 'now-15m', rangeTo: 'now' }); + hook.rerender({ rangeFrom: 'now-30m', rangeTo: 'now' }); - expect(hook.result.current.start).toEqual('2021-01-01T11:45:00.000Z'); + // times should not change, because rangeFrom/rangeTo did not change + expect(hook.result.current.start).toEqual('2021-01-01T11:30:00.000Z'); expect(hook.result.current.end).toEqual('2021-01-01T12:00:00.000Z'); - act(() => { - hook.result.current.refreshTimeRange(); - }); + hook.rerender({ rangeFrom: 'now-30m', rangeTo: 'now-15m' }); + + expect(hook.result.current.start).toEqual('2021-01-01T12:30:00.000Z'); + expect(hook.result.current.end).toEqual('2021-01-01T12:45:00.000Z'); + + hook.rerender({ rangeFrom: 'now-45m', rangeTo: 'now-30m' }); - expect(hook.result.current.start).toEqual('2021-01-01T12:45:00.000Z'); - expect(hook.result.current.end).toEqual('2021-01-01T13:00:00.000Z'); + expect(hook.result.current.start).toEqual('2021-01-01T12:15:00.000Z'); + expect(hook.result.current.end).toEqual('2021-01-01T12:30:00.000Z'); }); }); diff --git a/x-pack/plugins/apm/public/hooks/use_time_range.ts b/x-pack/plugins/apm/public/hooks/use_time_range.ts index 8263767a402dd9..940a83652addd3 100644 --- a/x-pack/plugins/apm/public/hooks/use_time_range.ts +++ b/x-pack/plugins/apm/public/hooks/use_time_range.ts @@ -6,7 +6,7 @@ */ import { isEqual } from 'lodash'; -import { useCallback, useRef, useState } from 'react'; +import { useRef } from 'react'; import { getDateRange } from '../context/url_params_context/helpers'; export function useTimeRange({ @@ -18,24 +18,19 @@ export function useTimeRange({ }) { const rangeRef = useRef({ rangeFrom, rangeTo }); - const [timeRangeId, setTimeRangeId] = useState(0); - const stateRef = useRef(getDateRange({ state: {}, rangeFrom, rangeTo })); - const updateParsedTime = useCallback(() => { + const updateParsedTime = () => { stateRef.current = getDateRange({ state: {}, rangeFrom, rangeTo }); - }, [rangeFrom, rangeTo]); + }; if (!isEqual(rangeRef.current, { rangeFrom, rangeTo })) { updateParsedTime(); } - const { start, end } = stateRef.current; + rangeRef.current = { rangeFrom, rangeTo }; - const refreshTimeRange = useCallback(() => { - updateParsedTime(); - setTimeRangeId((id) => id + 1); - }, [setTimeRangeId, updateParsedTime]); + const { start, end } = stateRef.current; if (!start || !end) { throw new Error('start and/or end were unexpectedly not set'); @@ -44,7 +39,5 @@ export function useTimeRange({ return { start, end, - refreshTimeRange, - timeRangeId, }; } From 25a167a53485f3c5a34d6a79ae80f5ea0cddc38f Mon Sep 17 00:00:00 2001 From: Vadim Yakhin Date: Wed, 18 Aug 2021 15:17:39 -0300 Subject: [PATCH 16/36] Limit the height of icon and logo on branding page (#109123) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../views/settings/components/branding_section.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.tsx index b153aed607f77a..30b450df91c179 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.tsx @@ -123,6 +123,7 @@ export const BrandingSection: React.FC = ({ {`${BRAND_TEXT} Date: Wed, 18 Aug 2021 11:48:31 -0700 Subject: [PATCH 17/36] [Security Solution] - Updating UI to work with new kibana privileges abstraction on alerts (#108961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Holy moly. What is happening in this PR? 🤷🏽‍♀️ Let's break it down: - Added a package `@kbn/alerts` - another one?! ...yes - This is meant to add shared hooks and components around alerts as data - `useGetUserAlertsPermissions` - accepts the Kibana capabilities object and returns whether the user has `read` and `crud` alerts privileges - `AlertsFeatureNoPermissions` - component displayed when user does not have alerts privileges - UI changes for user with NO alerts privileges - `Alerts` tab hidden in security solution side navigation - `Alerts` tab hidden in rule details page - UI changes for user with alerts READ ONLY privileges - alerts checkboxes hidden in alerts table - alerts bulk actions hidden in alerts table --- .i18nrc.json | 1 + package.json | 1 + packages/BUILD.bazel | 1 + packages/kbn-alerts/BUILD.bazel | 123 ++++++++++++++ packages/kbn-alerts/README.md | 7 + packages/kbn-alerts/babel.config.js | 19 +++ packages/kbn-alerts/jest.config.js | 13 ++ packages/kbn-alerts/package.json | 10 ++ packages/kbn-alerts/react/package.json | 5 + .../__snapshots__/index.test.tsx.snap | 70 ++++++++ .../kbn-alerts/src/empty_page/index.test.tsx | 37 +++++ packages/kbn-alerts/src/empty_page/index.tsx | 114 +++++++++++++ .../src/features_no_permissions/index.tsx | 47 ++++++ packages/kbn-alerts/src/hooks/index.ts | 8 + .../hooks/use_get_alerts_permissions/index.ts | 47 ++++++ packages/kbn-alerts/src/index.ts | 9 + packages/kbn-alerts/src/translations/index.ts | 22 +++ packages/kbn-alerts/tsconfig.browser.json | 22 +++ packages/kbn-alerts/tsconfig.json | 15 ++ .../src/alerts_as_data_rbac.ts | 2 +- .../server/alert_data_client/alerts_client.ts | 6 +- .../routes/__mocks__/request_responses.ts | 7 + .../server/routes/get_alert_index.test.ts | 76 +++++++++ .../server/routes/get_alert_index.ts | 31 +++- .../event_correlation_rule.spec.ts | 27 +-- .../cypress/screens/alerts.ts | 2 + .../security_solution/public/app/app.tsx | 7 +- .../public/app/deep_links/index.ts | 21 +++ .../events_viewer/events_viewer.test.tsx | 11 ++ .../common/components/events_viewer/index.tsx | 3 + .../index.test.tsx | 15 +- .../use_navigation_items.tsx | 13 +- .../components/user_privileges/index.tsx | 15 +- .../public/common/mock/test_providers.tsx | 7 +- .../components/alerts_table/index.tsx | 1 + .../alert_context_menu.test.tsx | 11 ++ .../timeline_actions/use_alerts_actions.tsx | 8 +- .../components/user_info/index.test.tsx | 8 +- .../detections/components/user_info/index.tsx | 23 ++- .../alerts/use_alerts_privileges.test.tsx | 9 +- .../alerts/use_alerts_privileges.tsx | 12 +- .../alerts/use_signal_index.test.tsx | 7 + .../public/detections/pages/alerts/index.tsx | 58 +++++++ .../detections/pages/alerts/translations.ts | 22 +++ .../detection_engine.test.tsx | 16 +- .../detection_engine/detection_engine.tsx | 156 ++++++++++-------- .../rules/details/index.test.tsx | 14 +- .../detection_engine/rules/details/index.tsx | 22 ++- .../public/detections/routes.tsx | 25 +-- .../public/overview/pages/overview.tsx | 43 +++-- .../security_solution/public/plugin.tsx | 19 +-- .../components/side_panel/index.test.tsx | 8 + .../timeline/body/actions/index.test.tsx | 11 ++ .../body/events/event_column_view.test.tsx | 12 ++ .../components/timeline/body/index.test.tsx | 3 + .../routes/index/create_index_route.ts | 2 +- .../routes/index/read_index_route.ts | 2 +- .../detections_admin/detections_role.json | 2 +- .../roles_users/hunter/detections_role.json | 2 +- .../platform_engineer/detections_role.json | 2 +- .../roles_users/reader/detections_role.json | 2 +- .../rule_author/detections_role.json | 2 +- .../soc_manager/detections_role.json | 2 +- .../t1_analyst/detections_role.json | 2 +- .../t2_analyst/detections_role.json | 2 +- .../public/components/t_grid/body/index.tsx | 15 +- .../components/t_grid/integrated/index.tsx | 3 + .../search_strategy/index_fields/index.ts | 16 +- .../server/search_strategy/timeline/index.ts | 15 +- .../security_and_spaces/tests/create_index.ts | 54 +++--- .../tests/basic/bulk_update_alerts.ts | 6 +- .../tests/basic/find_alerts.ts | 6 +- .../tests/basic/get_alert_by_id.ts | 6 +- .../tests/basic/update_alert.ts | 6 +- .../tests/trial/get_alert_by_id.ts | 6 +- .../spaces_only/tests/trial/update_alert.ts | 6 +- yarn.lock | 4 + 77 files changed, 1225 insertions(+), 240 deletions(-) create mode 100644 packages/kbn-alerts/BUILD.bazel create mode 100644 packages/kbn-alerts/README.md create mode 100644 packages/kbn-alerts/babel.config.js create mode 100644 packages/kbn-alerts/jest.config.js create mode 100644 packages/kbn-alerts/package.json create mode 100644 packages/kbn-alerts/react/package.json create mode 100644 packages/kbn-alerts/src/empty_page/__snapshots__/index.test.tsx.snap create mode 100644 packages/kbn-alerts/src/empty_page/index.test.tsx create mode 100644 packages/kbn-alerts/src/empty_page/index.tsx create mode 100644 packages/kbn-alerts/src/features_no_permissions/index.tsx create mode 100644 packages/kbn-alerts/src/hooks/index.ts create mode 100644 packages/kbn-alerts/src/hooks/use_get_alerts_permissions/index.ts create mode 100644 packages/kbn-alerts/src/index.ts create mode 100644 packages/kbn-alerts/src/translations/index.ts create mode 100644 packages/kbn-alerts/tsconfig.browser.json create mode 100644 packages/kbn-alerts/tsconfig.json create mode 100644 x-pack/plugins/rule_registry/server/routes/get_alert_index.test.ts create mode 100644 x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/pages/alerts/translations.ts diff --git a/.i18nrc.json b/.i18nrc.json index 235b65d7502f4f..3960831a3a4fa6 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -1,5 +1,6 @@ { "paths": { + "alerts": "packages/kbn-alerts/src", "autocomplete": "packages/kbn-securitysolution-autocomplete/src", "console": "src/plugins/console", "core": "src/core", diff --git a/package.json b/package.json index 9b67448b67479d..1fcda924722bf8 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "@hapi/podium": "^4.1.1", "@hapi/wreck": "^17.1.0", "@kbn/ace": "link:bazel-bin/packages/kbn-ace", + "@kbn/alerts": "link:bazel-bin/packages/kbn-alerts", "@kbn/analytics": "link:bazel-bin/packages/kbn-analytics", "@kbn/apm-config-loader": "link:bazel-bin/packages/kbn-apm-config-loader", "@kbn/apm-utils": "link:bazel-bin/packages/kbn-apm-utils", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 5a8aa75ee255e2..5c29b4a7eb64b8 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -7,6 +7,7 @@ filegroup( "//packages/elastic-eslint-config-kibana:build", "//packages/elastic-safer-lodash-set:build", "//packages/kbn-ace:build", + "//packages/kbn-alerts:build", "//packages/kbn-analytics:build", "//packages/kbn-apm-config-loader:build", "//packages/kbn-apm-utils:build", diff --git a/packages/kbn-alerts/BUILD.bazel b/packages/kbn-alerts/BUILD.bazel new file mode 100644 index 00000000000000..c585b4430bfcb2 --- /dev/null +++ b/packages/kbn-alerts/BUILD.bazel @@ -0,0 +1,123 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-alerts" + +PKG_REQUIRE_NAME = "@kbn/alerts" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + "src/**/*.tsx" + ], + exclude = [ + "**/*.test.*", + "**/*.mock.*", + "**/*.mocks.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "react/package.json", + "package.json", + "README.md", +] + +SRC_DEPS = [ + "//packages/kbn-babel-preset", + "//packages/kbn-dev-utils", + "//packages/kbn-i18n", + "@npm//@babel/core", + "@npm//babel-loader", + "@npm//@elastic/eui", + "@npm//react", + "@npm//resize-observer-polyfill", + "@npm//rxjs", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//typescript", + "@npm//@types/jest", + "@npm//@types/node", + "@npm//@types/react", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_config( + name = "tsconfig_browser", + src = "tsconfig.browser.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.browser.json", + "//:tsconfig.browser_bazel.json", + ], +) + +ts_project( + name = "tsc", + args = ["--pretty"], + srcs = SRCS, + deps = DEPS, + allow_js = True, + declaration = True, + declaration_dir = "target_types", + declaration_map = True, + out_dir = "target_node", + root_dir = "src", + source_map = True, + tsconfig = ":tsconfig", +) + +ts_project( + name = "tsc_browser", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + allow_js = True, + declaration = False, + out_dir = "target_web", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig_browser", +) + +js_library( + name = PKG_BASE_NAME, + package_name = PKG_REQUIRE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + visibility = ["//visibility:public"], + deps = [":tsc", ":tsc_browser"] + DEPS, +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/packages/kbn-alerts/README.md b/packages/kbn-alerts/README.md new file mode 100644 index 00000000000000..6c545d2e44dfbe --- /dev/null +++ b/packages/kbn-alerts/README.md @@ -0,0 +1,7 @@ +# AlertsFeatureNoPermissions + +Component displayed when a user with alerts permissions of `none` attempts to access alerts page. + +## useGetUserAlertsPermissions + +This hook parses through the uiCapabilities Kibana object to determine if the user has Kibana `read` or `crud` permissions for alerts. diff --git a/packages/kbn-alerts/babel.config.js b/packages/kbn-alerts/babel.config.js new file mode 100644 index 00000000000000..b4a118df51af51 --- /dev/null +++ b/packages/kbn-alerts/babel.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + env: { + web: { + presets: ['@kbn/babel-preset/webpack_preset'], + }, + node: { + presets: ['@kbn/babel-preset/node_preset'], + }, + }, + ignore: ['**/*.test.ts', '**/*.test.tsx'], +}; diff --git a/packages/kbn-alerts/jest.config.js b/packages/kbn-alerts/jest.config.js new file mode 100644 index 00000000000000..6ef365679ef74b --- /dev/null +++ b/packages/kbn-alerts/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-alerts'], +}; diff --git a/packages/kbn-alerts/package.json b/packages/kbn-alerts/package.json new file mode 100644 index 00000000000000..b52a6efc351390 --- /dev/null +++ b/packages/kbn-alerts/package.json @@ -0,0 +1,10 @@ +{ + "name": "@kbn/alerts", + "version": "1.0.0", + "description": "Alerts components and hooks", + "license": "SSPL-1.0 OR Elastic License 2.0", + "browser": "./target_web/index.js", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", + "private": true +} diff --git a/packages/kbn-alerts/react/package.json b/packages/kbn-alerts/react/package.json new file mode 100644 index 00000000000000..c5f222b5843acf --- /dev/null +++ b/packages/kbn-alerts/react/package.json @@ -0,0 +1,5 @@ +{ + "browser": "../target_web/react", + "main": "../target_node/react", + "types": "../target_types/react/index.d.ts" +} diff --git a/packages/kbn-alerts/src/empty_page/__snapshots__/index.test.tsx.snap b/packages/kbn-alerts/src/empty_page/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000000..59c877a20b8b59 --- /dev/null +++ b/packages/kbn-alerts/src/empty_page/__snapshots__/index.test.tsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EmptyPage component renders actions with descriptions 1`] = ` + + + + Do Something + + } + title={false} + /> + + + } + title={ +

+ My Super Title +

+ } +/> +`; + +exports[`EmptyPage component renders actions without descriptions 1`] = ` + + + + Do Something + + +
+ } + title={ +

+ My Super Title +

+ } +/> +`; diff --git a/packages/kbn-alerts/src/empty_page/index.test.tsx b/packages/kbn-alerts/src/empty_page/index.test.tsx new file mode 100644 index 00000000000000..be755b0c23c38a --- /dev/null +++ b/packages/kbn-alerts/src/empty_page/index.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { shallow } from 'enzyme'; +import React from 'react'; + +import { EmptyPage } from './'; + +describe('EmptyPage component', () => { + it('renders actions without descriptions', () => { + const actions = { + actions: { + label: 'Do Something', + url: 'my/url/from/nowwhere', + }, + }; + const EmptyComponent = shallow(); + expect(EmptyComponent).toMatchSnapshot(); + }); + + it('renders actions with descriptions', () => { + const actions = { + actions: { + description: 'My Description', + label: 'Do Something', + url: 'my/url/from/nowwhere', + }, + }; + const EmptyComponent = shallow(); + expect(EmptyComponent).toMatchSnapshot(); + }); +}); diff --git a/packages/kbn-alerts/src/empty_page/index.tsx b/packages/kbn-alerts/src/empty_page/index.tsx new file mode 100644 index 00000000000000..186f4c898309b1 --- /dev/null +++ b/packages/kbn-alerts/src/empty_page/index.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + IconType, + EuiCard, +} from '@elastic/eui'; +import React, { MouseEventHandler, ReactNode, useMemo } from 'react'; + +interface EmptyPageActions { + icon?: IconType; + label: string; + target?: string; + url: string; + descriptionTitle?: string; + description?: string; + fill?: boolean; + onClick?: MouseEventHandler; +} + +export type EmptyPageActionsProps = Record; + +interface EmptyPageProps { + actions: EmptyPageActionsProps; + 'data-test-subj'?: string; + message?: ReactNode; + title: string; + iconType?: IconType; +} + +const EmptyPageComponent = React.memo( + ({ actions, message, title, iconType, ...rest }) => { + const titles = Object.keys(actions); + const maxItemWidth = 283; + const renderActions = useMemo( + () => + Object.values(actions) + .filter((a) => a.label && a.url) + .map( + ( + { icon, label, target, url, descriptionTitle, description, onClick, fill = true }, + idx + ) => + descriptionTitle != null || description != null ? ( + + + {label} + + } + /> + + ) : ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {label} + + + ) + ), + [actions, titles] + ); + + return ( + {title}} + body={message &&

{message}

} + actions={{renderActions}} + {...rest} + /> + ); + } +); + +EmptyPageComponent.displayName = 'EmptyPageComponent'; + +export const EmptyPage = React.memo(EmptyPageComponent); +EmptyPage.displayName = 'EmptyPage'; diff --git a/packages/kbn-alerts/src/features_no_permissions/index.tsx b/packages/kbn-alerts/src/features_no_permissions/index.tsx new file mode 100644 index 00000000000000..6dabf06e87c475 --- /dev/null +++ b/packages/kbn-alerts/src/features_no_permissions/index.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IconType } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { EmptyPage } from '../empty_page'; +import * as i18n from '../translations'; + +interface AlertsFeatureNoPermissionsProps { + documentationUrl: string; + iconType: IconType; +} + +export const AlertsFeatureNoPermissions: React.FC = ({ + documentationUrl, + iconType, +}): JSX.Element => { + const actions = useMemo( + () => ({ + feature: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: documentationUrl, + target: '_blank', + }, + }), + [documentationUrl] + ); + + return ( + + ); +}; + +AlertsFeatureNoPermissions.displayName = 'AlertsFeatureNoPermissions'; diff --git a/packages/kbn-alerts/src/hooks/index.ts b/packages/kbn-alerts/src/hooks/index.ts new file mode 100644 index 00000000000000..aed5e979926860 --- /dev/null +++ b/packages/kbn-alerts/src/hooks/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export * from './use_get_alerts_permissions'; diff --git a/packages/kbn-alerts/src/hooks/use_get_alerts_permissions/index.ts b/packages/kbn-alerts/src/hooks/use_get_alerts_permissions/index.ts new file mode 100644 index 00000000000000..2f2c0967c32f29 --- /dev/null +++ b/packages/kbn-alerts/src/hooks/use_get_alerts_permissions/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useEffect, useState } from 'react'; + +// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715 +// import { Capabilities } from 'kibana/public'; +type Capabilities = any; +export interface UseGetUserAlertsPermissionsProps { + crud: boolean; + read: boolean; + loading: boolean; +} + +export const useGetUserAlertsPermissions = ( + uiCapabilities: Capabilities, + featureId: string +): UseGetUserAlertsPermissionsProps => { + const [alertsPermissions, setAlertsPermissions] = useState({ + crud: false, + read: false, + loading: true, + }); + + useEffect(() => { + const capabilitiesCanUserCRUD: boolean = + typeof uiCapabilities[featureId].crud_alerts === 'boolean' + ? uiCapabilities[featureId].crud_alerts + : false; + const capabilitiesCanUserRead: boolean = + typeof uiCapabilities[featureId].read_alerts === 'boolean' + ? uiCapabilities[featureId].read_alerts + : false; + setAlertsPermissions({ + crud: capabilitiesCanUserCRUD, + read: capabilitiesCanUserRead, + loading: false, + }); + }, [featureId, uiCapabilities]); + + return alertsPermissions; +}; diff --git a/packages/kbn-alerts/src/index.ts b/packages/kbn-alerts/src/index.ts new file mode 100644 index 00000000000000..304516f54ba17e --- /dev/null +++ b/packages/kbn-alerts/src/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export * from './hooks'; +export * from './features_no_permissions'; diff --git a/packages/kbn-alerts/src/translations/index.ts b/packages/kbn-alerts/src/translations/index.ts new file mode 100644 index 00000000000000..2d540ba1452d91 --- /dev/null +++ b/packages/kbn-alerts/src/translations/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export const FEATURE_NO_PERMISSIONS_TITLE = i18n.translate('alerts.noPermissionsTitle', { + defaultMessage: 'Kibana feature privileges required', +}); + +export const ALERTS_FEATURE_NO_PERMISSIONS_MSG = i18n.translate('alerts.noPermissionsMessage', { + defaultMessage: + 'To view alerts, you must have privileges for the Alerts feature in the Kibana space. For more information, contact your Kibana administrator.', +}); + +export const GO_TO_DOCUMENTATION = i18n.translate('alerts.documentationTitle', { + defaultMessage: 'View documentation', +}); diff --git a/packages/kbn-alerts/tsconfig.browser.json b/packages/kbn-alerts/tsconfig.browser.json new file mode 100644 index 00000000000000..bb58f529eb0bbf --- /dev/null +++ b/packages/kbn-alerts/tsconfig.browser.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.browser_bazel.json", + "compilerOptions": { + "allowJs": true, + "outDir": "./target_web", + "declaration": false, + "isolatedModules": true, + "sourceMap": true, + "sourceRoot": "../../../../../packages/kbn-alerts/src", + "types": [ + "jest", + "node" + ], + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + ], + "exclude": [ + "**/__fixtures__/**/*" + ] +} \ No newline at end of file diff --git a/packages/kbn-alerts/tsconfig.json b/packages/kbn-alerts/tsconfig.json new file mode 100644 index 00000000000000..6a791ca2e58445 --- /dev/null +++ b/packages/kbn-alerts/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "allowJs": true, + "declarationDir": "./target_types", + "outDir": "target_node", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-alerts/src", + "rootDir": "src", + "types": ["jest", "node", "resize-observer-polyfill"] + }, + "include": ["src/**/*"] +} \ No newline at end of file diff --git a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts index 719301bce8e068..48f9d705da27d8 100644 --- a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts +++ b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts @@ -31,7 +31,7 @@ export const mapConsumerToIndexName: Record = logs: '.alerts-observability.logs', infrastructure: '.alerts-observability.metrics', observability: '.alerts-observability', - siem: ['.alerts-security.alerts', '.siem-signals'], + siem: '.alerts-security.alerts', uptime: '.alerts-observability.uptime', }; export type ValidFeatureId = keyof typeof mapConsumerToIndexName; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index 75b63fe51f7cb2..d2a8b914d10b64 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -666,7 +666,11 @@ export class AlertsClient { const toReturn = Array.from(authorizedFeatures).flatMap((feature) => { if (isValidFeatureId(feature)) { - return mapConsumerToIndexName[feature]; + if (feature === 'siem') { + return `${mapConsumerToIndexName[feature]}-${this.spaceId}`; + } else { + return `${mapConsumerToIndexName[feature]}`; + } } return []; }); diff --git a/x-pack/plugins/rule_registry/server/routes/__mocks__/request_responses.ts b/x-pack/plugins/rule_registry/server/routes/__mocks__/request_responses.ts index 228fcf491994fd..d591e01c9fff6a 100644 --- a/x-pack/plugins/rule_registry/server/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/rule_registry/server/routes/__mocks__/request_responses.ts @@ -8,6 +8,13 @@ import { BASE_RAC_ALERTS_API_PATH } from '../../../common/constants'; import { requestMock } from './server'; +export const getReadIndexRequest = () => + requestMock.create({ + method: 'get', + path: `${BASE_RAC_ALERTS_API_PATH}/index`, + query: { features: 'siem' }, + }); + export const getReadRequest = () => requestMock.create({ method: 'get', diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_index.test.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_index.test.ts new file mode 100644 index 00000000000000..b8ef01847d8eac --- /dev/null +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_index.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { BASE_RAC_ALERTS_API_PATH } from '../../common/constants'; +import { getAlertsIndexRoute } from './get_alert_index'; +import { requestContextMock } from './__mocks__/request_context'; +import { getReadIndexRequest } from './__mocks__/request_responses'; +import { requestMock, serverMock } from './__mocks__/server'; + +describe('getAlertsIndexRoute', () => { + let server: ReturnType; + let { clients, context } = requestContextMock.createTools(); + + beforeEach(async () => { + server = serverMock.create(); + ({ clients, context } = requestContextMock.createTools()); + + clients.rac.getAuthorizedAlertsIndices.mockResolvedValue(['alerts-security.alerts']); + + getAlertsIndexRoute(server.router); + }); + + test('returns 200 when querying for index', async () => { + const response = await server.inject(getReadIndexRequest(), context); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ index_name: ['alerts-security.alerts'] }); + }); + + describe('request validation', () => { + test('rejects invalid query params', async () => { + await expect( + server.inject( + requestMock.create({ + method: 'get', + path: `${BASE_RAC_ALERTS_API_PATH}/index`, + query: { features: 4 }, + }), + context + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Request was rejected with message: 'Invalid value \\"4\\" supplied to \\"features\\"'"` + ); + }); + + test('rejects unknown query params', async () => { + await expect( + server.inject( + requestMock.create({ + method: 'get', + path: `${BASE_RAC_ALERTS_API_PATH}/index`, + query: { boop: 'siem' }, + }), + context + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Request was rejected with message: 'invalid keys \\"boop\\"'"` + ); + }); + }); + + test('returns error status if rac client "getAuthorizedAlertsIndices" fails', async () => { + clients.rac.getAuthorizedAlertsIndices.mockRejectedValue(new Error('Unable to get index')); + const response = await server.inject(getReadIndexRequest(), context); + + expect(response.status).toEqual(500); + expect(response.body).toEqual({ + attributes: { success: false }, + message: 'Unable to get index', + }); + }); +}); diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_index.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_index.ts index f3b0b9181c60fc..758057c21c43ef 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_index.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_index.ts @@ -6,9 +6,11 @@ */ import { IRouter } from 'kibana/server'; +import * as t from 'io-ts'; import { id as _id } from '@kbn/securitysolution-io-ts-list-types'; import { transformError } from '@kbn/securitysolution-es-utils'; import { validFeatureIds } from '@kbn/rule-data-utils'; +import { buildRouteValidation } from './utils/route_validation'; import { RacRequestHandlerContext } from '../types'; import { BASE_RAC_ALERTS_API_PATH } from '../../common/constants'; @@ -17,7 +19,15 @@ export const getAlertsIndexRoute = (router: IRouter) = router.get( { path: `${BASE_RAC_ALERTS_API_PATH}/index`, - validate: false, + validate: { + query: buildRouteValidation( + t.exact( + t.partial({ + features: t.string, + }) + ) + ), + }, options: { tags: ['access:rac'], }, @@ -25,7 +35,10 @@ export const getAlertsIndexRoute = (router: IRouter) = async (context, request, response) => { try { const alertsClient = await context.rac.getAlertsClient(); - const indexName = await alertsClient.getAuthorizedAlertsIndices(validFeatureIds); + const { features } = request.query; + const indexName = await alertsClient.getAuthorizedAlertsIndices( + features?.split(',') ?? validFeatureIds + ); return response.ok({ body: { index_name: indexName }, }); @@ -38,15 +51,15 @@ export const getAlertsIndexRoute = (router: IRouter) = ...contentType, }; - return response.custom({ + return response.customError({ headers: defaultedHeaders, statusCode: err.statusCode, - body: Buffer.from( - JSON.stringify({ - message: err.message, - status_code: err.statusCode, - }) - ), + body: { + message: err.message, + attributes: { + success: false, + }, + }, }); } } diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts index 016e8b3f8f0a1b..5e77366618d082 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts @@ -8,7 +8,7 @@ import { formatMitreAttackDescription } from '../../helpers/rules'; import { getEqlRule, getEqlSequenceRule, getIndexPatterns } from '../../objects/rule'; -import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts'; +import { ALERT_DATA_GRID, NUMBER_OF_ALERTS } from '../../screens/alerts'; import { CUSTOM_RULES_BTN, RISK_SCORE, @@ -161,11 +161,13 @@ describe('Detection rules, EQL', () => { waitForAlertsToPopulate(); cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); - // EuiDataGrid doesn't seem to have a way to apply data-test-subj to the individual cells - // Also, text detailing the row and column shows up in this search so switched 'have.text' to 'contains' - cy.get(ALERT_GRID_CELL).eq(3).contains(this.rule.name); - cy.get(ALERT_GRID_CELL).eq(4).contains(this.rule.severity.toLowerCase()); - cy.get(ALERT_GRID_CELL).eq(5).contains(this.rule.riskScore); + cy.get(ALERT_DATA_GRID) + .invoke('text') + .then((text) => { + expect(text).contains(this.rule.name); + expect(text).contains(this.rule.severity.toLowerCase()); + expect(text).contains(this.rule.riskScore); + }); }); }); @@ -213,10 +215,13 @@ describe('Detection rules, sequence EQL', () => { waitForAlertsToPopulate(); cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfSequenceAlerts); - // EuiDataGrid doesn't seem to have a way to apply data-test-subj to the individual cells - // Also, text detailing the row and column shows up in this search so switched 'have.text' to 'contains' - cy.get(ALERT_GRID_CELL).eq(3).contains(this.rule.name); - cy.get(ALERT_GRID_CELL).eq(4).contains(this.rule.severity.toLowerCase()); - cy.get(ALERT_GRID_CELL).eq(5).contains(this.rule.riskScore); + cy.get(ALERT_DATA_GRID) + .invoke('text') + .then((text) => { + cy.log('ALERT_DATA_GRID', text); + expect(text).contains(this.rule.name); + expect(text).contains(this.rule.severity.toLowerCase()); + expect(text).contains(this.rule.riskScore); + }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/cypress/screens/alerts.ts index 71f7184230f2be..675a25641a2bd2 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts.ts @@ -27,6 +27,8 @@ export const ALERT_RULE_RISK_SCORE = '[data-test-subj="formatted-field-signal.ru export const ALERT_RULE_SEVERITY = '[data-test-subj="formatted-field-signal.rule.severity"]'; +export const ALERT_DATA_GRID = '[data-test-subj="dataGridWrapper"]'; + export const CLOSE_ALERT_BTN = '[data-test-subj="close-alert-status"]'; export const CLOSE_SELECTED_ALERTS_BTN = '[data-test-subj="close-alert-status"]'; diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index 0cba9341cbce18..8abe19ed26d8d4 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -41,7 +41,10 @@ const StartAppComponent: FC = ({ onAppLeave, store, }) => { - const { i18n } = useKibana().services; + const { + i18n, + application: { capabilities }, + } = useKibana().services; const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); return ( @@ -51,7 +54,7 @@ const StartAppComponent: FC = ({ - + { + if (detLink.id === SecurityPageName.alerts) { + return { + ...detLink, + navLinkStatus: capabilities.siem.read_alerts + ? AppNavLinkStatus.visible + : AppNavLinkStatus.hidden, + searchable: capabilities.siem.read_alerts === true, + }; + } + return detLink; + }), + ] + : [], + }; default: return link; } diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index f808e31ae6d2ce..29ba8fc0bd541c 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -33,12 +33,23 @@ import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_fe import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions'; import { mockTimelines } from '../../mock/mock_timelines_plugin'; +jest.mock('@kbn/alerts', () => ({ + useGetUserAlertsPermissions: () => ({ + loading: false, + crud: true, + read: true, + }), +})); + jest.mock('../../lib/kibana', () => ({ useKibana: () => ({ services: { application: { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), + capabilities: { + siem: { crud_alerts: true, read_alerts: true }, + }, }, uiSettings: { get: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 1b21eafc2ba2b9..d85c4464af986b 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -64,6 +64,7 @@ export interface OwnProps { rowRenderers: RowRenderer[]; utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; additionalFilters?: React.ReactNode; + hasAlertsCrud?: boolean; } type Props = OwnProps & PropsFromRedux; @@ -103,6 +104,7 @@ const StatefulEventsViewerComponent: React.FC = ({ additionalFilters, // If truthy, the graph viewer (Resolver) is showing graphEventId, + hasAlertsCrud = false, }) => { const { timelines: timelinesUi } = useKibana().services; const { @@ -164,6 +166,7 @@ const StatefulEventsViewerComponent: React.FC = ({ filters: globalFilters, globalFullScreen, graphOverlay, + hasAlertsCrud, indexNames: selectedPatterns, indexPattern, isLive, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index 4bd5a436847925..1f98d3b8261293 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { useGetUserAlertsPermissions } from '@kbn/alerts'; import { renderHook } from '@testing-library/react-hooks'; import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; @@ -23,7 +24,7 @@ jest.mock('../../../lib/kibana'); jest.mock('../../../hooks/use_selector'); jest.mock('../../../hooks/use_experimental_features'); jest.mock('../../../utils/route/use_route_spy'); - +jest.mock('@kbn/alerts'); describe('useSecuritySolutionNavigation', () => { const mockUrlState = { [CONSTANTS.appQuery]: { query: 'host.name:"security-solution-es"', language: 'kuery' }, @@ -75,12 +76,24 @@ describe('useSecuritySolutionNavigation', () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); (useDeepEqualSelector as jest.Mock).mockReturnValue({ urlState: mockUrlState }); (useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy); + (useGetUserAlertsPermissions as jest.Mock).mockReturnValue({ + loading: false, + crud: true, + read: true, + }); + (useKibana as jest.Mock).mockReturnValue({ services: { application: { navigateToApp: jest.fn(), getUrlForApp: (appId: string, options?: { path?: string; deepLinkId?: boolean }) => `${appId}/${options?.deepLinkId ?? ''}${options?.path ?? ''}`, + capabilities: { + siem: { + crud_alerts: true, + read_alerts: true, + }, + }, }, chrome: { setBreadcrumbs: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index feeeacf6124e82..ca574a58727615 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -7,12 +7,15 @@ import React, { useCallback, useMemo } from 'react'; import { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_nav_types'; +import { useGetUserAlertsPermissions } from '@kbn/alerts'; + import { securityNavGroup } from '../../../../app/home/home_navigations'; import { getSearch } from '../helpers'; import { PrimaryNavigationItemsProps } from './types'; -import { useGetUserCasesPermissions } from '../../../lib/kibana'; +import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana'; import { useNavigation } from '../../../lib/kibana/hooks'; import { NavTab } from '../types'; +import { SERVER_APP_ID } from '../../../../../common/constants'; export const usePrimaryNavigationItems = ({ navTabs, @@ -60,7 +63,9 @@ export const usePrimaryNavigationItems = ({ }; function usePrimaryNavigationItemsToDisplay(navTabs: Record) { + const uiCapabilities = useKibana().services.application.capabilities; const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; + const hasAlertsReadPermissions = useGetUserAlertsPermissions(uiCapabilities, SERVER_APP_ID); return useMemo( () => [ { @@ -70,7 +75,9 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { }, { ...securityNavGroup.detect, - items: [navTabs.alerts, navTabs.rules, navTabs.exceptions], + items: hasAlertsReadPermissions.read + ? [navTabs.alerts, navTabs.rules, navTabs.exceptions] + : [navTabs.rules, navTabs.exceptions], }, { ...securityNavGroup.explore, @@ -85,6 +92,6 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { items: [navTabs.endpoints, navTabs.trusted_apps, navTabs.event_filters], }, ], - [navTabs, hasCasesReadPermissions] + [navTabs, hasCasesReadPermissions, hasAlertsReadPermissions] ); } diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx index 5a33297f04f9a4..fa9de895f7d03d 100644 --- a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx @@ -7,39 +7,50 @@ import React, { createContext, useContext } from 'react'; import { DeepReadonly } from 'utility-types'; +import { useGetUserAlertsPermissions } from '@kbn/alerts'; + +import { Capabilities } from '../../../../../../../src/core/public'; import { useFetchDetectionEnginePrivileges } from '../../../detections/components/user_privileges/use_fetch_detection_engine_privileges'; import { useFetchListPrivileges } from '../../../detections/components/user_privileges/use_fetch_list_privileges'; import { EndpointPrivileges, useEndpointPrivileges } from './use_endpoint_privileges'; +import { SERVER_APP_ID } from '../../../../common/constants'; export interface UserPrivilegesState { listPrivileges: ReturnType; detectionEnginePrivileges: ReturnType; endpointPrivileges: EndpointPrivileges; + alertsPrivileges: ReturnType; } export const initialUserPrivilegesState = (): UserPrivilegesState => ({ listPrivileges: { loading: false, error: undefined, result: undefined }, detectionEnginePrivileges: { loading: false, error: undefined, result: undefined }, endpointPrivileges: { loading: true, canAccessEndpointManagement: false, canAccessFleet: false }, + alertsPrivileges: { loading: false, read: false, crud: false }, }); const UserPrivilegesContext = createContext(initialUserPrivilegesState()); interface UserPrivilegesProviderProps { + kibanaCapabilities: Capabilities; children: React.ReactNode; } -export const UserPrivilegesProvider = ({ children }: UserPrivilegesProviderProps) => { +export const UserPrivilegesProvider = ({ + kibanaCapabilities, + children, +}: UserPrivilegesProviderProps) => { const listPrivileges = useFetchListPrivileges(); const detectionEnginePrivileges = useFetchDetectionEnginePrivileges(); const endpointPrivileges = useEndpointPrivileges(); - + const alertsPrivileges = useGetUserAlertsPermissions(kibanaCapabilities, SERVER_APP_ID); return ( {children} diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 647ce4dcd15e87..0c227ac6395698 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -14,6 +14,7 @@ import { Provider as ReduxStoreProvider } from 'react-redux'; import { Store } from 'redux'; import { BehaviorSubject } from 'rxjs'; import { ThemeProvider } from 'styled-components'; +import { Capabilities } from 'src/core/public'; import { createStore, State } from '../store'; import { mockGlobalState } from './global_state'; @@ -73,7 +74,11 @@ const TestProvidersWithPrivilegesComponent: React.FC = ({ ({ eui: euiDarkVars, darkMode: true })}> - + {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 8b987de04cba27..fc3e1e7f2d69bd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -396,6 +396,7 @@ export const AlertsTableComponent: React.FC = ({ start={from} utilityBar={utilityBarCallback} additionalFilters={additionalFiltersComponent} + hasAlertsCrud={hasIndexWrite} /> ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx index b49c5602bc140b..101ba99f0bba6b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx @@ -13,6 +13,14 @@ import React from 'react'; import { Ecs } from '../../../../../common/ecs'; import { mockTimelines } from '../../../../common/mock/mock_timelines_plugin'; +jest.mock('@kbn/alerts', () => ({ + useGetUserAlertsPermissions: () => ({ + loading: false, + crud: true, + read: true, + }), +})); + const ecsRowData: Ecs = { _id: '1', agent: { type: ['blah'] } }; const props = { @@ -36,6 +44,9 @@ jest.mock('../../../../common/lib/kibana', () => ({ useKibana: () => ({ services: { timelines: { ...mockTimelines }, + application: { + capabilities: { siem: { crud_alerts: true, read_alerts: true } }, + }, }, }), useGetUserCasesPermissions: jest.fn().mockReturnValue({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx index 3380ab314be025..780cb65ed13d31 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alerts_actions.tsx @@ -7,12 +7,14 @@ import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; +import { useGetUserAlertsPermissions } from '@kbn/alerts'; import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; import { timelineActions } from '../../../../timelines/store/timeline'; import { SetEventsDeletedProps, SetEventsLoadingProps } from '../types'; import { useStatusBulkActionItems } from '../../../../../../timelines/public'; - +import { useKibana } from '../../../../common/lib/kibana'; +import { SERVER_APP_ID } from '../../../../../common/constants'; interface Props { alertStatus?: Status; closePopover: () => void; @@ -31,6 +33,8 @@ export const useAlertsActions = ({ refetch, }: Props) => { const dispatch = useDispatch(); + const uiCapabilities = useKibana().services.application.capabilities; + const alertsPrivileges = useGetUserAlertsPermissions(uiCapabilities, SERVER_APP_ID); const onStatusUpdate = useCallback(() => { closePopover(); @@ -64,6 +68,6 @@ export const useAlertsActions = ({ }); return { - actionItems, + actionItems: alertsPrivileges.crud ? actionItems : [], }; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx index bb9ec01399f8d1..9972233dce3511 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { useUserInfo, ManageUserInfo } from './index'; +import { Capabilities } from 'src/core/public'; import { useKibana } from '../../../common/lib/kibana'; import * as api from '../../containers/detection_engine/alerts/api'; @@ -46,6 +47,7 @@ describe('useUserInfo', () => { hasIndexManage: null, hasIndexMaintenance: null, hasIndexWrite: null, + hasIndexRead: null, hasIndexUpdateDelete: null, isAuthenticated: null, isSignalIndexExists: null, @@ -65,7 +67,11 @@ describe('useUserInfo', () => { }); const wrapper = ({ children }: { children: JSX.Element }) => ( - + {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx index 9a8fc5e27a5e4e..da6df631d951eb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx @@ -18,6 +18,7 @@ export interface State { hasIndexManage: boolean | null; hasIndexMaintenance: boolean | null; hasIndexWrite: boolean | null; + hasIndexRead: boolean | null; hasIndexUpdateDelete: boolean | null; isSignalIndexExists: boolean | null; isAuthenticated: boolean | null; @@ -32,6 +33,7 @@ export const initialState: State = { hasIndexManage: null, hasIndexMaintenance: null, hasIndexWrite: null, + hasIndexRead: null, hasIndexUpdateDelete: null, isSignalIndexExists: null, isAuthenticated: null, @@ -55,6 +57,10 @@ export type Action = type: 'updateHasIndexWrite'; hasIndexWrite: boolean | null; } + | { + type: 'updateHasIndexRead'; + hasIndexRead: boolean | null; + } | { type: 'updateHasIndexUpdateDelete'; hasIndexUpdateDelete: boolean | null; @@ -110,6 +116,12 @@ export const userInfoReducer = (state: State, action: Action): State => { hasIndexWrite: action.hasIndexWrite, }; } + case 'updateHasIndexRead': { + return { + ...state, + hasIndexRead: action.hasIndexRead, + }; + } case 'updateHasIndexUpdateDelete': { return { ...state, @@ -178,6 +190,7 @@ export const useUserInfo = (): State => { hasIndexManage, hasIndexMaintenance, hasIndexWrite, + hasIndexRead, hasIndexUpdateDelete, isSignalIndexExists, isAuthenticated, @@ -194,8 +207,9 @@ export const useUserInfo = (): State => { hasEncryptionKey: isApiEncryptionKey, hasIndexManage: hasApiIndexManage, hasIndexMaintenance: hasApiIndexMaintenance, - hasIndexWrite: hasApiIndexWrite, hasIndexUpdateDelete: hasApiIndexUpdateDelete, + hasIndexWrite: hasApiIndexWrite, + hasIndexRead: hasApiIndexRead, } = useAlertsPrivileges(); const { loading: indexNameLoading, @@ -228,6 +242,12 @@ export const useUserInfo = (): State => { } }, [dispatch, loading, hasIndexWrite, hasApiIndexWrite]); + useEffect(() => { + if (!loading && hasIndexRead !== hasApiIndexRead && hasApiIndexRead != null) { + dispatch({ type: 'updateHasIndexRead', hasIndexRead: hasApiIndexRead }); + } + }, [dispatch, loading, hasIndexRead, hasApiIndexRead]); + useEffect(() => { if ( !loading && @@ -334,6 +354,7 @@ export const useUserInfo = (): State => { hasIndexManage, hasIndexMaintenance, hasIndexWrite, + hasIndexRead, hasIndexUpdateDelete, signalIndexName, signalIndexMappingOutdated, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx index f3afe833652866..64d9db80316a95 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx @@ -87,6 +87,7 @@ const userPrivilegesInitial: ReturnType = { error: undefined, }, endpointPrivileges: { loading: true, canAccessEndpointManagement: false, canAccessFleet: false }, + alertsPrivileges: { loading: true, crud: false, read: false }, }; describe('usePrivilegeUser', () => { @@ -161,8 +162,8 @@ describe('usePrivilegeUser', () => { hasEncryptionKey: true, hasIndexManage: false, hasIndexMaintenance: true, - hasIndexRead: true, - hasIndexWrite: true, + hasIndexRead: false, + hasIndexWrite: false, hasIndexUpdateDelete: true, isAuthenticated: true, loading: false, @@ -186,8 +187,8 @@ describe('usePrivilegeUser', () => { hasEncryptionKey: true, hasIndexManage: true, hasIndexMaintenance: true, - hasIndexRead: true, - hasIndexWrite: true, + hasIndexRead: false, + hasIndexWrite: false, hasIndexUpdateDelete: true, isAuthenticated: true, loading: false, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx index 005224a80c1891..1d9b8228b5070c 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx @@ -35,7 +35,7 @@ export const useAlertsPrivileges = (): UseAlertsPrivelegesReturn => { hasIndexUpdateDelete: null, hasIndexMaintenance: null, }); - const { detectionEnginePrivileges } = useUserPrivileges(); + const { detectionEnginePrivileges, alertsPrivileges } = useUserPrivileges(); useEffect(() => { if (detectionEnginePrivileges.error != null) { @@ -62,17 +62,13 @@ export const useAlertsPrivileges = (): UseAlertsPrivelegesReturn => { hasEncryptionKey: privilege.has_encryption_key, hasIndexManage: privilege.index[indexName].manage && privilege.cluster.manage, hasIndexMaintenance: privilege.index[indexName].maintenance, - hasIndexRead: privilege.index[indexName].read, - hasIndexWrite: - privilege.index[indexName].create || - privilege.index[indexName].create_doc || - privilege.index[indexName].index || - privilege.index[indexName].write, + hasIndexRead: alertsPrivileges.read, + hasIndexWrite: alertsPrivileges.crud, hasIndexUpdateDelete: privilege.index[indexName].write, }); } } - }, [detectionEnginePrivileges.result]); + }, [detectionEnginePrivileges.result, alertsPrivileges]); return { loading: detectionEnginePrivileges.loading, ...privileges }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx index ade83fed4fd6bb..6d68dae3758661 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -14,6 +14,13 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); jest.mock('../../../../common/hooks/use_app_toasts'); jest.mock('../../../../common/components/user_privileges/use_endpoint_privileges'); +jest.mock('@kbn/alerts', () => ({ + useGetUserAlertsPermissions: () => ({ + loading: false, + crud: true, + read: true, + }), +})); describe('useSignalIndex', () => { let appToastsMock: jest.Mocked>; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx new file mode 100644 index 00000000000000..dbd59d25102384 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/index.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { Route, Switch } from 'react-router-dom'; +import { useGetUserAlertsPermissions } from '@kbn/alerts'; + +import { ALERTS_PATH, SecurityPageName, SERVER_APP_ID } from '../../../../common/constants'; +import { NotFoundPage } from '../../../app/404'; +import * as i18n from './translations'; +import { TrackApplicationView } from '../../../../../../../src/plugins/usage_collection/public'; +import { DetectionEnginePage } from '../../pages/detection_engine/detection_engine'; +import { useKibana } from '../../../common/lib/kibana'; +import { SpyRoute } from '../../../common/utils/route/spy_routes'; + +const AlertsRoute = () => ( + + + + +); + +const AlertsContainerComponent: React.FC = () => { + const { + chrome, + application: { capabilities }, + } = useKibana().services; + const userPermissions = useGetUserAlertsPermissions(capabilities, SERVER_APP_ID); + + useEffect(() => { + // if the user is read only then display the glasses badge in the global navigation header + if (userPermissions != null && !userPermissions.crud && userPermissions.read) { + chrome.setBadge({ + text: i18n.READ_ONLY_BADGE_TEXT, + tooltip: i18n.READ_ONLY_BADGE_TOOLTIP, + iconType: 'glasses', + }); + } + + // remove the icon after the component unmounts + return () => { + chrome.setBadge(); + }; + }, [userPermissions, chrome]); + + return ( + + + + + ); +}; + +export const Alerts = React.memo(AlertsContainerComponent); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/alerts/translations.ts new file mode 100644 index 00000000000000..734e93925e5365 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const READ_ONLY_BADGE_TEXT = i18n.translate( + 'xpack.securitySolution.alerts.badge.readOnly.text', + { + defaultMessage: 'Read only', + } +); + +export const READ_ONLY_BADGE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.alerts.badge.readOnly.tooltip', + { + defaultMessage: 'Unable to update alerts', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index badad82a2f7601..a92f4d706dc7c9 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -67,6 +67,9 @@ jest.mock('../../../common/lib/kibana', () => { services: { application: { navigateToUrl: jest.fn(), + capabilities: { + siem: { crud_alerts: true, read_alerts: true }, + }, }, timelines: { ...mockTimelines }, data: { @@ -74,6 +77,13 @@ jest.mock('../../../common/lib/kibana', () => { filterManager: jest.fn().mockReturnValue({}), }, }, + docLinks: { + links: { + siem: { + gettingStarted: 'link', + }, + }, + }, }, }), useToasts: jest.fn().mockReturnValue({ @@ -94,7 +104,11 @@ const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage) describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); - (useUserData as jest.Mock).mockReturnValue([{}]); + (useUserData as jest.Mock).mockReturnValue([ + { + hasIndexRead: true, + }, + ]); (useSourcererScope as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index e117a0d640fd70..d6531198c1884f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -17,6 +17,8 @@ import { noop } from 'lodash/fp'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; import { Dispatch } from 'redux'; +import { AlertsFeatureNoPermissions } from '@kbn/alerts'; + import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { isTab } from '../../../../../timelines/public'; @@ -112,12 +114,11 @@ const DetectionEnginePageComponent: React.FC = ({ const [ { loading: userInfoLoading, - isSignalIndexExists, isAuthenticated: isUserAuthenticated, hasEncryptionKey, signalIndexName, hasIndexWrite, - hasIndexMaintenance, + hasIndexRead, }, ] = useUserData(); const { @@ -131,6 +132,7 @@ const DetectionEnginePageComponent: React.FC = ({ const { application: { navigateToUrl }, timelines: timelinesUi, + docLinks, } = useKibana().services; const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); @@ -256,12 +258,12 @@ const DetectionEnginePageComponent: React.FC = ({ ); } - if (!loading && (isSignalIndexExists === false || needsListsConfiguration)) { + if (!loading && (indicesExist === false || needsListsConfiguration)) { return ( @@ -276,77 +278,89 @@ const DetectionEnginePageComponent: React.FC = ({ {indicesExist ? ( - - - - - - - - - {i18n.BUTTON_MANAGE_RULES} - - - - - - - - - {timelinesUi.getLastUpdated({ updatedAt: updatedAt || 0, showUpdating: loading })} - - - - - - - + {hasIndexRead ? ( + <> + + + + + + + + {i18n.BUTTON_MANAGE_RULES} + + + + + + + + + {timelinesUi.getLastUpdated({ + updatedAt: updatedAt || 0, + showUpdating: loading, + })} + + + + + + + - - - - + + + + - - + + - + + + ) : ( + - + )} ) : ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx index 01867e4e53d5ce..c1d674ce456ff7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx @@ -45,7 +45,13 @@ jest.mock('../../../../../common/containers/use_global_time', () => ({ setQuery: jest.fn(), }), })); - +jest.mock('@kbn/alerts', () => ({ + useGetUserAlertsPermissions: () => ({ + loading: false, + crud: true, + read: true, + }), +})); jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -65,8 +71,12 @@ jest.mock('../../../../../common/lib/kibana', () => { useKibana: () => ({ services: { application: { + ...original.useKibana().services.application, navigateToUrl: jest.fn(), - capabilities: { actions: jest.fn().mockReturnValue({}) }, + capabilities: { + actions: jest.fn().mockReturnValue({}), + siem: { crud_alerts: true, read_alerts: true }, + }, }, timelines: { ...mockTimelines }, data: { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 8c8f2d00e746a2..4c3db2ae62be3c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -197,6 +197,7 @@ const RuleDetailsPageComponent: React.FC = ({ hasEncryptionKey, canUserCRUD, hasIndexWrite, + hasIndexRead, hasIndexMaintenance, signalIndexName, }, @@ -227,6 +228,7 @@ const RuleDetailsPageComponent: React.FC = ({ // This is used to re-trigger api rule status when user de/activate rule const [ruleEnabled, setRuleEnabled] = useState(null); const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.alerts); + const [pageTabs, setTabs] = useState(ruleDetailTabs); const { aboutRuleData, modifiedAboutRuleDetailsData, defineRuleData, scheduleRuleData } = rule != null ? getStepsData({ rule, detailsView: true }) @@ -273,6 +275,16 @@ const RuleDetailsPageComponent: React.FC = ({ } }, [maybeRule]); + useEffect(() => { + if (!hasIndexRead) { + setTabs(ruleDetailTabs.filter(({ id }) => id !== RuleDetailTabs.alerts)); + setRuleDetailTab(RuleDetailTabs.exceptions); + } else { + setTabs(ruleDetailTabs); + setRuleDetailTab(RuleDetailTabs.alerts); + } + }, [hasIndexRead]); + const title = useMemo( () => ( <> @@ -395,7 +407,7 @@ const RuleDetailsPageComponent: React.FC = ({ const tabs = useMemo( () => ( - {ruleDetailTabs.map((tab) => ( + {pageTabs.map((tab) => ( setRuleDetailTab(tab.id)} isSelected={tab.id === ruleDetailTab} @@ -408,7 +420,7 @@ const RuleDetailsPageComponent: React.FC = ({ ))} ), - [ruleDetailTab, setRuleDetailTab] + [ruleDetailTab, setRuleDetailTab, pageTabs] ); const ruleIndices = useMemo( () => @@ -457,7 +469,7 @@ const RuleDetailsPageComponent: React.FC = ({ ); } else if ( currentStatus?.status === 'failed' && - ruleDetailTab === RuleDetailTabs.alerts && + (ruleDetailTab === RuleDetailTabs.alerts || ruleDetailTab === RuleDetailTabs.failures) && currentStatus?.last_failure_at != null ) { return ( @@ -468,7 +480,7 @@ const RuleDetailsPageComponent: React.FC = ({ ); } else if ( (currentStatus?.status === 'warning' || currentStatus?.status === 'partial failure') && - ruleDetailTab === RuleDetailTabs.alerts && + (ruleDetailTab === RuleDetailTabs.alerts || ruleDetailTab === RuleDetailTabs.failures) && currentStatus?.last_success_at != null ) { return ( @@ -750,7 +762,7 @@ const RuleDetailsPageComponent: React.FC = ({ {tabs} - {ruleDetailTab === RuleDetailTabs.alerts && ( + {ruleDetailTab === RuleDetailTabs.alerts && hasIndexRead && ( <> diff --git a/x-pack/plugins/security_solution/public/detections/routes.tsx b/x-pack/plugins/security_solution/public/detections/routes.tsx index f0128577cb268d..5b7b85c0183aaa 100644 --- a/x-pack/plugins/security_solution/public/detections/routes.tsx +++ b/x-pack/plugins/security_solution/public/detections/routes.tsx @@ -6,28 +6,11 @@ */ import React from 'react'; -import { Redirect, RouteProps, RouteComponentProps, Route, Switch } from 'react-router-dom'; -import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; -import { ALERTS_PATH, DETECTIONS_PATH, SecurityPageName } from '../../common/constants'; -import { NotFoundPage } from '../app/404'; +import { Redirect, RouteProps, RouteComponentProps } from 'react-router-dom'; +import { ALERTS_PATH, DETECTIONS_PATH } from '../../common/constants'; +import { Alerts } from './pages/alerts'; -import { SpyRoute } from '../common/utils/route/spy_routes'; - -import { DetectionEnginePage } from './pages/detection_engine/detection_engine'; - -const AlertsRoute = () => ( - - - - -); - -const renderAlertsRoutes = () => ( - - - - -); +const renderAlertsRoutes = () => ; const DetectionsRedirects = ({ location }: RouteComponentProps) => location.pathname === DETECTIONS_PATH ? ( diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 9c0b1ea87e1f97..8deb1b93f97286 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -70,7 +70,10 @@ const OverviewComponent = () => { setDismissMessage(true); addMessage('management', 'dismissEndpointNotice'); }, [addMessage]); - const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet; + const { + endpointPrivileges: { canAccessFleet }, + alertsPrivileges, + } = useUserPrivileges(); const isThreatIntelModuleEnabled = useIsThreatIntelModuleEnabled(); return ( <> @@ -95,23 +98,27 @@ const OverviewComponent = () => { - - - - - - - - + {alertsPrivileges?.read && ( + <> + + + + + + + + + + )} ({ + useGetUserAlertsPermissions: () => ({ + loading: false, + crud: true, + read: true, + }), +})); + describe('Details Panel Component', () => { const state: State = { ...mockGlobalState }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx index b55c8080e30d18..b982c2240ac7ce 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx @@ -21,12 +21,23 @@ jest.mock('../../../../../common/hooks/use_selector', () => ({ useShallowEqualSelector: jest.fn(), })); +jest.mock('@kbn/alerts', () => ({ + useGetUserAlertsPermissions: () => ({ + loading: false, + crud: true, + read: true, + }), +})); + jest.mock('../../../../../common/lib/kibana', () => ({ useKibana: () => ({ services: { application: { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), + capabilities: { + siem: { crud_alerts: true, read_alerts: true }, + }, }, uiSettings: { get: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 74dbf28694390b..d20c62348f07fc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -24,11 +24,23 @@ import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin' jest.mock('../../../../../common/hooks/use_experimental_features'); const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; +jest.mock('@kbn/alerts', () => ({ + useGetUserAlertsPermissions: () => ({ + loading: false, + crud: true, + read: true, + }), +})); jest.mock('../../../../../common/hooks/use_selector'); jest.mock('../../../../../common/lib/kibana', () => ({ useKibana: () => ({ services: { timelines: { ...mockTimelines }, + application: { + capabilities: { + siem: { crud_alerts: true, read_alerts: true }, + }, + }, }, }), useToasts: jest.fn().mockReturnValue({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 3f805a21afa6b7..1bfefbd1197a08 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -37,6 +37,9 @@ jest.mock('../../../../common/lib/kibana', () => { application: { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), + capabilities: { + siem: { crud_alerts: true, read_alerts: true }, + }, }, uiSettings: { get: jest.fn(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts index 7dad03ed7e14e3..ab7ff26d9d8757 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_index_route.ts @@ -89,7 +89,7 @@ export const createDetectionIndex = async ( ruleDataService: RuleDataPluginService, ruleRegistryEnabled: boolean ): Promise => { - const esClient = context.core.elasticsearch.client.asCurrentUser; + const esClient = context.core.elasticsearch.client.asInternalUser; const spaceId = siemClient.getSpaceId(); if (!siemClient) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts index 4cfedd5dcaa011..c36dade4bb9d04 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts @@ -30,7 +30,7 @@ export const readIndexRoute = (router: SecuritySolutionPluginRouter, config: Con const siemResponse = buildSiemResponse(response); try { - const esClient = context.core.elasticsearch.client.asCurrentUser; + const esClient = context.core.elasticsearch.client.asInternalUser; const siemClient = context.securitySolution?.getAppClient(); if (!siemClient) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json index 1291c9274271e2..ef3a3bef324f92 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json @@ -32,7 +32,7 @@ { "feature": { "ml": ["all"], - "siem": ["all"], + "siem": ["all", "read_alerts", "crud_alerts"], "actions": ["read"], "builtInAlerts": ["all"], "dev_tools": ["all"] diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json index 62d0343e57afb6..f9d2c68e6878a3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json @@ -37,7 +37,7 @@ { "feature": { "ml": ["read"], - "siem": ["all"], + "siem": ["all", "read_alerts", "crud_alerts"], "actions": ["read"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json index d5c84dc5811de5..5c6188b053d20b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json @@ -32,7 +32,7 @@ { "feature": { "ml": ["all"], - "siem": ["all"], + "siem": ["all", "read_alerts", "crud_alerts"], "actions": ["all"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json index 72c9369b31ae8f..d04251542d11ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json @@ -25,7 +25,7 @@ { "feature": { "ml": ["read"], - "siem": ["read"], + "siem": ["read", "read_alerts"], "actions": ["read"], "builtInAlerts": ["read"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json index 1f3b8ceaf7b36a..f7b8818d6c0049 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json @@ -35,7 +35,7 @@ { "feature": { "ml": ["read"], - "siem": ["all"], + "siem": ["all", "read_alerts", "crud_alerts"], "actions": ["read"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json index ae18fb7d3f1b54..324fb2737f24f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json @@ -35,7 +35,7 @@ { "feature": { "ml": ["read"], - "siem": ["all"], + "siem": ["all", "read_alerts", "crud_alerts"], "actions": ["all"], "builtInAlerts": ["all"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json index fad86e4a3572a1..90232bdb53fed4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json @@ -25,7 +25,7 @@ { "feature": { "ml": ["read"], - "siem": ["read"], + "siem": ["read", "read_alerts"], "actions": ["read"], "builtInAlerts": ["read"] }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json index af3ba06a97d6a2..9885ba0ee610b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json @@ -27,7 +27,7 @@ { "feature": { "ml": ["read"], - "siem": ["read"], + "siem": ["read", "read_alerts"], "actions": ["read"], "builtInAlerts": ["read"] }, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 12a0f6bfc2b648..8dbc7f34b530ef 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -102,6 +102,7 @@ interface OwnProps { totalPages: number; trailingControlColumns?: ControlColumnProps[]; unit?: (total: number) => React.ReactNode; + hasAlertsCrud?: boolean; } const basicUnit = (n: number) => i18n.UNIT(n); @@ -273,6 +274,7 @@ export const BodyComponent = React.memo( totalPages, trailingControlColumns = EMPTY_CONTROL_COLUMNS, unit = basicUnit, + hasAlertsCrud, }) => { const dispatch = useDispatch(); const getManageTimeline = useMemo(() => tGridSelectors.getManageTimelineById(), []); @@ -337,6 +339,10 @@ export const BodyComponent = React.memo( }, [bulkActions]); const showBulkActions = useMemo(() => { + if (!hasAlertsCrud) { + return false; + } + if (selectedCount === 0 || !showCheckboxes) { return false; } @@ -344,7 +350,7 @@ export const BodyComponent = React.memo( return bulkActions; } return bulkActions.alertStatusActions ?? true; - }, [selectedCount, showCheckboxes, bulkActions]); + }, [hasAlertsCrud, selectedCount, showCheckboxes, bulkActions]); const alertToolbar = useMemo( () => ( @@ -666,7 +672,10 @@ const makeMapStateToProps = () => { ) => ColumnHeaderOptions[] = memoizeOne(getColumnHeaders); const getTGrid = tGridSelectors.getTGridByIdSelector(); - const mapStateToProps = (state: TimelineState, { browserFields, id }: OwnProps) => { + const mapStateToProps = ( + state: TimelineState, + { browserFields, id, hasAlertsCrud }: OwnProps + ) => { const timeline: TGridModel = getTGrid(state, id); const { columns, @@ -685,7 +694,7 @@ const makeMapStateToProps = () => { loadingEventIds, id, selectedEventIds, - showCheckboxes, + showCheckboxes: hasAlertsCrud === true && showCheckboxes, sort, }; }; diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index 9062e8b14228d9..b84eff4d2142c7 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -137,6 +137,7 @@ export interface TGridIntegratedProps { trailingControlColumns?: ControlColumnProps[]; data?: DataPublicPluginStart; tGridEventRenderedViewEnabled: boolean; + hasAlertsCrud: boolean; } const TGridIntegratedComponent: React.FC = ({ @@ -173,6 +174,7 @@ const TGridIntegratedComponent: React.FC = ({ trailingControlColumns, tGridEventRenderedViewEnabled, data, + hasAlertsCrud, }) => { const dispatch = useDispatch(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; @@ -338,6 +340,7 @@ const TGridIntegratedComponent: React.FC = ({ ) : ( <> => { - const indexPatternsFetcher = new IndexPatternsFetcher(esClient.asCurrentUser); + const indexPatternsFetcherAsCurrentUser = new IndexPatternsFetcher(esClient.asCurrentUser); + const indexPatternsFetcherAsInternalUser = new IndexPatternsFetcher(esClient.asInternalUser); + const dedupeIndices = dedupeIndexName(request.indices); const responsesIndexFields = await Promise.all( @@ -63,9 +65,15 @@ export const requestIndexFieldSearch = async ( }); return get(searchResponse, 'body.hits.total.value', 0) > 0; } else { - return indexPatternsFetcher.getFieldsForWildcard({ - pattern: index, - }); + if (index.startsWith('.alerts-security.alerts')) { + return indexPatternsFetcherAsInternalUser.getFieldsForWildcard({ + pattern: index, + }); + } else { + return indexPatternsFetcherAsCurrentUser.getFieldsForWildcard({ + pattern: index, + }); + } } }) .map((p) => p.catch((e) => false)) diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts index a2a5a3bc8a49ac..bd70d989d97dde 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/index.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { ALERT_RULE_CONSUMER, ALERT_RULE_TYPE_ID, SPACE_IDS } from '@kbn/rule-data-utils'; +import { + AlertConsumers as CONSUMERS, + ALERT_RULE_CONSUMER, + ALERT_RULE_TYPE_ID, + SPACE_IDS, +} from '@kbn/rule-data-utils'; import { map, mergeMap, catchError } from 'rxjs/operators'; import { from } from 'rxjs'; @@ -138,7 +143,13 @@ const timelineAlertsSearchStrategy = ({ }) => { // Based on what solution alerts you want to see, figures out what corresponding // index to query (ex: siem --> .alerts-security.alerts) - const indices = alertConsumers.flatMap((consumer) => `${mapConsumerToIndexName[consumer]}*`); + const indices = alertConsumers.flatMap((consumer) => { + if (consumer === CONSUMERS.SIEM) { + return request.defaultIndex ?? request.indexType; + } + + return `${mapConsumerToIndexName[consumer]}`; + }); const requestWithAlertsIndices = { ...request, defaultIndex: indices, indexName: indices }; // Note: Alerts RBAC are built off of the alerting's authorization class, which diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts index 4748e39cd3a46e..8eacd4231a92e5 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts @@ -82,15 +82,15 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); }); - it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + it('should be able to create a signal index when it has not been created yet', async () => { const { body } = await supertestWithoutAuth .post(DETECTION_ENGINE_INDEX_URL) .set('kbn-xsrf', 'true') .auth(role, 'changeme') .send() - .expect(403); - expect(body.message).to.match(/^security_exception/); - expect(body.status_code).to.eql(403); + .expect(200); + + expect(body).to.eql({ acknowledged: true }); }); it('should be able to read the index name and status as not being outdated', async () => { @@ -103,7 +103,7 @@ export default ({ getService }: FtrProviderContext) => { .send() .expect(200); expect(body).to.eql({ - index_mapping_outdated: null, + index_mapping_outdated: false, name: `${DEFAULT_SIGNALS_INDEX}-default`, }); }); @@ -129,15 +129,15 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); }); - it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + it('should be able to create a signal index when it has not been created yet.', async () => { const { body } = await supertestWithoutAuth .post(DETECTION_ENGINE_INDEX_URL) .set('kbn-xsrf', 'true') .auth(role, 'changeme') .send() - .expect(403); - expect(body.message).to.match(/^security_exception/); - expect(body.status_code).to.eql(403); + .expect(200); + + expect(body).to.eql({ acknowledged: true }); }); it('should be able to read the index name and status as not being outdated', async () => { @@ -150,7 +150,7 @@ export default ({ getService }: FtrProviderContext) => { .send() .expect(200); expect(body).to.eql({ - index_mapping_outdated: null, + index_mapping_outdated: false, name: `${DEFAULT_SIGNALS_INDEX}-default`, }); }); @@ -226,15 +226,15 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); }); - it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + it('should be able to create a signal index when it has not been created yet', async () => { const { body } = await supertestWithoutAuth .post(DETECTION_ENGINE_INDEX_URL) .set('kbn-xsrf', 'true') .auth(role, 'changeme') .send() - .expect(403); - expect(body.message).to.match(/^security_exception/); - expect(body.status_code).to.eql(403); + .expect(200); + + expect(body).to.eql({ acknowledged: true }); }); it('should be able to read the index name and status as not being outdated', async () => { @@ -272,16 +272,16 @@ export default ({ getService }: FtrProviderContext) => { .expect(404); expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); }); - - it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + // here + it('should be able to create a signal index when it has not been created yet', async () => { const { body } = await supertestWithoutAuth .post(DETECTION_ENGINE_INDEX_URL) .set('kbn-xsrf', 'true') .auth(role, 'changeme') .send() - .expect(403); - expect(body.message).to.match(/^security_exception/); - expect(body.status_code).to.eql(403); + .expect(200); + + expect(body).to.eql({ acknowledged: true }); }); it('should be able to read the index name and status as not being outdated', async () => { @@ -294,7 +294,7 @@ export default ({ getService }: FtrProviderContext) => { .send() .expect(200); expect(body).to.eql({ - index_mapping_outdated: null, + index_mapping_outdated: false, name: `${DEFAULT_SIGNALS_INDEX}-default`, }); }); @@ -370,15 +370,14 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); }); - it('should NOT be able to create a signal index when it has not been created yet. Should return a 401 unauthorized', async () => { + it('should be able to create a signal index when it has not been created yet', async () => { const { body } = await supertestWithoutAuth .post(DETECTION_ENGINE_INDEX_URL) .set('kbn-xsrf', 'true') .auth(role, 'changeme') .send() - .expect(403); - expect(body.message).to.match(/^security_exception/); - expect(body.status_code).to.eql(403); + .expect(200); + expect(body).to.eql({ acknowledged: true }); }); it('should be able to read the index name and status as being outdated.', async () => { @@ -417,15 +416,14 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); }); - it('should NOT be able to create a signal index when it has not been created yet. Should return a 401 unauthorized', async () => { + it('should be able to create a signal index when it has not been created yet', async () => { const { body } = await supertestWithoutAuth .post(DETECTION_ENGINE_INDEX_URL) .set('kbn-xsrf', 'true') .auth(role, 'changeme') .send() - .expect(403); - expect(body.message).to.match(/^security_exception/); - expect(body.status_code).to.eql(403); + .expect(200); + expect(body).to.eql({ acknowledged: true }); }); it('should be able to read the index name and status as being outdated.', async () => { diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts index b203bb8659a77a..815b346c15d1a5 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts @@ -81,10 +81,10 @@ export default ({ getService }: FtrProviderContext) => { .auth(user.username, user.password) .set('kbn-xsrf', 'true') .expect(200); - const securitySolution = indexNames?.index_name?.find( - (indexName) => indexName === SECURITY_SOLUTION_ALERT_INDEX + const securitySolution = indexNames?.index_name?.find((indexName) => + indexName.startsWith(SECURITY_SOLUTION_ALERT_INDEX) ); - expect(securitySolution).to.eql(SECURITY_SOLUTION_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below + expect(securitySolution).to.eql(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`); // assert this here so we can use constants in the dynamically-defined test cases below }; describe('Alert - Bulk Update - RBAC - spaces', () => { diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/find_alerts.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/find_alerts.ts index 409fbbde5cac90..f6fcff64db3a3d 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/find_alerts.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/find_alerts.ts @@ -80,10 +80,10 @@ export default ({ getService }: FtrProviderContext) => { .auth(user.username, user.password) .set('kbn-xsrf', 'true') .expect(200); - const securitySolution = indexNames?.index_name?.find( - (indexName) => indexName === SECURITY_SOLUTION_ALERT_INDEX + const securitySolution = indexNames?.index_name?.find((indexName) => + indexName.startsWith(SECURITY_SOLUTION_ALERT_INDEX) ); - expect(securitySolution).to.eql(SECURITY_SOLUTION_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below + expect(securitySolution).to.eql(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`); // assert this here so we can use constants in the dynamically-defined test cases below }; describe('Alert - Find - RBAC - spaces', () => { diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_by_id.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_by_id.ts index 15729f83ebcf10..1f1da40eee70ee 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_by_id.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_by_id.ts @@ -80,10 +80,10 @@ export default ({ getService }: FtrProviderContext) => { .auth(user.username, user.password) .set('kbn-xsrf', 'true') .expect(200); - const securitySolution = indexNames?.index_name?.find( - (indexName) => indexName === SECURITY_SOLUTION_ALERT_INDEX + const securitySolution = indexNames?.index_name?.find((indexName) => + indexName.startsWith(SECURITY_SOLUTION_ALERT_INDEX) ); - expect(securitySolution).to.eql(SECURITY_SOLUTION_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below + expect(securitySolution).to.eql(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`); // assert this here so we can use constants in the dynamically-defined test cases below }; describe('Alerts - GET - RBAC - spaces', () => { diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts index 917cb31869bccf..348aedb258f7ef 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts @@ -80,10 +80,10 @@ export default ({ getService }: FtrProviderContext) => { .auth(user.username, user.password) .set('kbn-xsrf', 'true') .expect(200); - const securitySolution = indexNames?.index_name?.find( - (indexName) => indexName === SECURITY_SOLUTION_ALERT_INDEX + const securitySolution = indexNames?.index_name?.find((indexName) => + indexName.startsWith(SECURITY_SOLUTION_ALERT_INDEX) ); - expect(securitySolution).to.eql(SECURITY_SOLUTION_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below + expect(securitySolution).to.eql(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`); // assert this here so we can use constants in the dynamically-defined test cases below }; describe('Alert - Update - RBAC - spaces', () => { diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/get_alert_by_id.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/get_alert_by_id.ts index 2543f8d73ff054..373acd9e8a8840 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/get_alert_by_id.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/get_alert_by_id.ts @@ -45,10 +45,10 @@ export default ({ getService }: FtrProviderContext) => { .get(`${getSpaceUrlPrefix(SPACE1)}${ALERTS_INDEX_URL}`) .set('kbn-xsrf', 'true') .expect(200); - const securitySolution = indexNames?.index_name?.find( - (indexName) => indexName === SECURITY_SOLUTION_ALERT_INDEX + const securitySolution = indexNames?.index_name?.find((indexName) => + indexName.startsWith(SECURITY_SOLUTION_ALERT_INDEX) ); - expect(securitySolution).to.eql(SECURITY_SOLUTION_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below + expect(securitySolution).to.eql(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`); // assert this here so we can use constants in the dynamically-defined test cases below }; describe('Alerts - GET - RBAC', () => { diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/update_alert.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/update_alert.ts index f5179b253b7017..24642059139408 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/update_alert.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/update_alert.ts @@ -45,10 +45,10 @@ export default ({ getService }: FtrProviderContext) => { .get(`${getSpaceUrlPrefix(SPACE1)}${ALERTS_INDEX_URL}`) .set('kbn-xsrf', 'true') .expect(200); - const securitySolution = indexNames?.index_name?.find( - (indexName) => indexName === SECURITY_SOLUTION_ALERT_INDEX + const securitySolution = indexNames?.index_name?.find((indexName) => + indexName.startsWith(SECURITY_SOLUTION_ALERT_INDEX) ); - expect(securitySolution).to.eql(SECURITY_SOLUTION_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below + expect(securitySolution).to.eql(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`); // assert this here so we can use constants in the dynamically-defined test cases below }; describe('Alert - Update - RBAC - spaces', () => { diff --git a/yarn.lock b/yarn.lock index 8a71ff74738e1b..eaf99ed839ceaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2726,6 +2726,10 @@ version "0.0.0" uid "" +"@kbn/alerts@link:bazel-bin/packages/kbn-alerts": + version "0.0.0" + uid "" + "@kbn/analytics@link:bazel-bin/packages/kbn-analytics": version "0.0.0" uid "" From dd9dd527187c3a1bafcfabb6db9af4057f9cd481 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Aug 2021 12:51:31 -0600 Subject: [PATCH 18/36] [maps] remove tile_map, region_map, and maps_legacy plugins (#105326) * [maps] remove tile_map plugin * initial bounds * update embeddable query context * start editor * remove tile_map from tsconfig and i18n cleanup * implement view in maps button * tslint * remove empty lines * remove tileMap from limits.yml * remove region_map and maps_legacy plugins * region_map vis with Map embeddable * make MapComponent * lint * clean up * shorten text * lint * remove region_map from interpreter functional tests * update docs * add migration for removing ui_settings * remove tile_map and region_map functional tests * tslint * call handlers.done when layers are loaded * fix visualize create menu test * eslint * add owner comment to ui_settings/saved_objects/migrations.ts * remove deleted plugins from codeowners * review feedback * use correct value for TILE_MAP_RENDER * down select mapModules for getLayerDescriptors callback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 4 - .i18nrc.json | 3 - docs/developer/plugin-list.asciidoc | 12 - docs/management/advanced-options.asciidoc | 13 +- package.json | 4 - packages/kbn-optimizer/limits.yml | 5 +- .../saved_objects/migrations.test.ts | 35 + .../ui_settings/saved_objects/migrations.ts | 23 + src/plugins/maps_legacy/README.md | 7 - src/plugins/maps_legacy/config.ts | 13 - src/plugins/maps_legacy/jest.config.js | 13 - src/plugins/maps_legacy/kibana.json | 13 - .../maps_legacy/public/common/types.ts | 23 - .../legacy_map_deprecation_message.tsx | 68 -- .../components/wms_internal_options.tsx | 205 ------ .../public/components/wms_options.tsx | 88 --- src/plugins/maps_legacy/public/index.ts | 31 - .../maps_legacy/public/kibana_services.ts | 29 - .../public/lazy_load_bundle/index.ts | 30 - .../public/lazy_load_bundle/lazy/index.ts | 12 - src/plugins/maps_legacy/public/leaflet.js | 22 - .../public/map/_leaflet_overrides.scss | 158 ---- .../maps_legacy/public/map/_legend.scss | 33 - .../public/map/base_maps_visualization.js | 244 ------- .../maps_legacy/public/map/color_util.d.ts | 11 - .../maps_legacy/public/map/color_util.js | 25 - .../public/map/geohash_columns.test.ts | 16 - .../maps_legacy/public/map/geohash_columns.ts | 27 - src/plugins/maps_legacy/public/map/index.scss | 2 - .../maps_legacy/public/map/kibana_map.js | 683 ------------------ .../public/map/kibana_map_layer.d.ts | 25 - .../public/map/kibana_map_layer.js | 38 - .../maps_legacy/public/map/precision.ts | 63 -- .../public/map/zoom_to_precision.ts | 49 -- src/plugins/maps_legacy/public/plugin.ts | 61 -- .../maps_legacy/public/tooltip_provider.d.ts | 9 - .../maps_legacy/public/tooltip_provider.js | 32 - src/plugins/maps_legacy/server/index.ts | 43 -- src/plugins/maps_legacy/server/ui_settings.ts | 102 --- src/plugins/maps_legacy/tsconfig.json | 14 - src/plugins/region_map/README.md | 5 - src/plugins/region_map/jest.config.js | 13 - src/plugins/region_map/kibana.json | 21 - src/plugins/region_map/package.json | 4 - .../__snapshots__/region_map_fn.test.ts.snap | 55 -- .../region_map/public/choropleth_layer.js | 500 ------------- .../region_map/public/components/index.tsx | 18 - .../public/components/region_map_options.tsx | 211 ------ .../public/get_deprecation_message.tsx | 73 -- src/plugins/region_map/public/index.ts | 19 - .../region_map/public/kibana_services.ts | 39 - src/plugins/region_map/public/plugin.ts | 120 --- .../region_map/public/region_map_fn.test.ts | 51 -- .../region_map/public/region_map_fn.ts | 63 -- .../region_map/public/region_map_renderer.tsx | 41 -- .../region_map/public/region_map_type.ts | 143 ---- .../region_map/public/region_map_types.ts | 31 - .../public/region_map_visualization.js | 239 ------ .../public/region_map_visualization.scss | 4 - .../region_map_visualization_component.tsx | 92 --- src/plugins/region_map/public/to_ast.ts | 48 -- .../region_map/public/tooltip_formatter.js | 29 - src/plugins/region_map/public/util.ts | 15 - src/plugins/region_map/server/index.ts | 18 - src/plugins/region_map/server/ui_settings.ts | 31 - src/plugins/region_map/tsconfig.json | 15 - src/plugins/tile_map/README.md | 5 - src/plugins/tile_map/jest.config.js | 13 - src/plugins/tile_map/kibana.json | 21 - src/plugins/tile_map/package.json | 4 - .../__snapshots__/tile_map_fn.test.ts.snap | 55 -- .../tile_map/public/components/collections.ts | 65 -- .../tile_map/public/components/index.tsx | 14 - .../public/components/tile_map_options.tsx | 103 --- src/plugins/tile_map/public/css_filters.js | 29 - src/plugins/tile_map/public/geohash_layer.js | 172 ----- .../public/get_deprecation_message.tsx | 70 -- src/plugins/tile_map/public/index.ts | 14 - .../tile_map/public/markers/geohash_grid.js | 24 - .../tile_map/public/markers/heatmap.js | 183 ----- .../tile_map/public/markers/scaled_circles.js | 236 ------ .../tile_map/public/markers/shaded_circles.js | 52 -- src/plugins/tile_map/public/plugin.ts | 102 --- src/plugins/tile_map/public/services.ts | 32 - .../tile_map/public/tile_map_fn.test.ts | 85 --- src/plugins/tile_map/public/tile_map_fn.ts | 71 -- .../tile_map/public/tile_map_renderer.tsx | 41 -- src/plugins/tile_map/public/tile_map_type.ts | 95 --- .../tile_map/public/tile_map_visualization.js | 256 ------- .../public/tile_map_visualization.scss | 4 - .../tile_map_visualization_component.tsx | 92 --- src/plugins/tile_map/public/to_ast.ts | 48 -- .../tile_map/public/tooltip_formatter.js | 34 - src/plugins/tile_map/public/types.ts | 46 -- .../public/utils/convert_to_geojson.ts | 122 ---- .../public/utils/decode_geo_hash.test.ts | 16 - .../tile_map/public/utils/decode_geo_hash.ts | 89 --- .../tile_map/public/utils/grid_dimensions.ts | 28 - src/plugins/tile_map/public/utils/index.ts | 10 - .../tile_map/public/utils/map_types.ts | 14 - src/plugins/tile_map/server/index.ts | 12 - src/plugins/tile_map/tsconfig.json | 15 - .../public/data_model/vega_parser.ts | 4 +- .../apps/dashboard/dashboard_state.ts | 35 - .../apps/dashboard/embeddable_library.ts | 39 - test/functional/apps/visualize/_region_map.ts | 105 --- test/functional/apps/visualize/_tile_map.ts | 225 ------ test/functional/apps/visualize/index.ts | 2 - .../functional/page_objects/dashboard_page.ts | 1 - test/functional/page_objects/index.ts | 2 - test/functional/page_objects/tile_map_page.ts | 93 --- .../functional/page_objects/visualize_page.ts | 16 - .../screenshots/baseline/partial_test_3.png | Bin 10750 -> 0 bytes .../snapshots/baseline/partial_test_3.json | 1 - .../snapshots/session/partial_test_3.json | 1 - .../test_suites/run_pipeline/basic.ts | 7 +- tsconfig.json | 2 +- x-pack/plugins/maps/kibana.json | 1 + x-pack/plugins/maps/public/_index.scss | 3 +- .../create_region_map_layer_descriptor.ts | 26 +- .../create_tile_map_layer_descriptor.ts | 20 +- .../map_container/map_container.tsx | 32 +- .../maps/public/embeddable/_index.scss | 8 + .../maps/public/embeddable/map_component.tsx | 111 +++ .../maps/public/embeddable/map_embeddable.tsx | 25 + .../plugins/maps/public/embeddable/types.ts | 6 + .../maps/public/lazy_load_bundle/index.ts | 37 +- .../public/legacy_visualizations/index.ts | 9 + .../legacy_visualizations/region_map/index.ts | 10 + .../region_map/region_map_editor.tsx | 34 + .../region_map/region_map_fn.ts | 62 ++ .../region_map/region_map_renderer.tsx | 36 + .../region_map/region_map_vis_type.ts | 43 ++ .../region_map/region_map_visualization.tsx | 47 ++ .../region_map/to_ast.ts | 30 + .../legacy_visualizations/region_map/types.ts | 29 + .../legacy_visualizations/region_map/utils.ts | 49 ++ .../legacy_visualizations/tile_map/index.ts | 10 + .../tile_map/tile_map_editor.tsx | 34 + .../tile_map/tile_map_fn.ts | 62 ++ .../tile_map/tile_map_renderer.tsx | 36 + .../tile_map/tile_map_vis_type.ts | 44 ++ .../tile_map/tile_map_visualization.tsx | 47 ++ .../legacy_visualizations/tile_map/to_ast.ts | 30 + .../legacy_visualizations/tile_map/types.ts | 29 + .../legacy_visualizations/tile_map/utils.ts | 44 ++ .../legacy_visualizations/view_in_maps.tsx | 51 ++ x-pack/plugins/maps/public/plugin.ts | 18 + .../routes/map_page/map_app/map_app.tsx | 1 + .../translations/translations/ja-JP.json | 76 -- .../translations/translations/zh-CN.json | 77 -- .../apps/maps/visualize_create_menu.js | 4 +- yarn.lock | 20 - 153 files changed, 1032 insertions(+), 7160 deletions(-) delete mode 100644 src/plugins/maps_legacy/README.md delete mode 100644 src/plugins/maps_legacy/config.ts delete mode 100644 src/plugins/maps_legacy/jest.config.js delete mode 100644 src/plugins/maps_legacy/kibana.json delete mode 100644 src/plugins/maps_legacy/public/common/types.ts delete mode 100644 src/plugins/maps_legacy/public/components/legacy_map_deprecation_message.tsx delete mode 100644 src/plugins/maps_legacy/public/components/wms_internal_options.tsx delete mode 100644 src/plugins/maps_legacy/public/components/wms_options.tsx delete mode 100644 src/plugins/maps_legacy/public/index.ts delete mode 100644 src/plugins/maps_legacy/public/kibana_services.ts delete mode 100644 src/plugins/maps_legacy/public/lazy_load_bundle/index.ts delete mode 100644 src/plugins/maps_legacy/public/lazy_load_bundle/lazy/index.ts delete mode 100644 src/plugins/maps_legacy/public/leaflet.js delete mode 100644 src/plugins/maps_legacy/public/map/_leaflet_overrides.scss delete mode 100644 src/plugins/maps_legacy/public/map/_legend.scss delete mode 100644 src/plugins/maps_legacy/public/map/base_maps_visualization.js delete mode 100644 src/plugins/maps_legacy/public/map/color_util.d.ts delete mode 100644 src/plugins/maps_legacy/public/map/color_util.js delete mode 100644 src/plugins/maps_legacy/public/map/geohash_columns.test.ts delete mode 100644 src/plugins/maps_legacy/public/map/geohash_columns.ts delete mode 100644 src/plugins/maps_legacy/public/map/index.scss delete mode 100644 src/plugins/maps_legacy/public/map/kibana_map.js delete mode 100644 src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts delete mode 100644 src/plugins/maps_legacy/public/map/kibana_map_layer.js delete mode 100644 src/plugins/maps_legacy/public/map/precision.ts delete mode 100644 src/plugins/maps_legacy/public/map/zoom_to_precision.ts delete mode 100644 src/plugins/maps_legacy/public/plugin.ts delete mode 100644 src/plugins/maps_legacy/public/tooltip_provider.d.ts delete mode 100644 src/plugins/maps_legacy/public/tooltip_provider.js delete mode 100644 src/plugins/maps_legacy/server/index.ts delete mode 100644 src/plugins/maps_legacy/server/ui_settings.ts delete mode 100644 src/plugins/maps_legacy/tsconfig.json delete mode 100644 src/plugins/region_map/README.md delete mode 100644 src/plugins/region_map/jest.config.js delete mode 100644 src/plugins/region_map/kibana.json delete mode 100644 src/plugins/region_map/package.json delete mode 100644 src/plugins/region_map/public/__snapshots__/region_map_fn.test.ts.snap delete mode 100644 src/plugins/region_map/public/choropleth_layer.js delete mode 100644 src/plugins/region_map/public/components/index.tsx delete mode 100644 src/plugins/region_map/public/components/region_map_options.tsx delete mode 100644 src/plugins/region_map/public/get_deprecation_message.tsx delete mode 100644 src/plugins/region_map/public/index.ts delete mode 100644 src/plugins/region_map/public/kibana_services.ts delete mode 100644 src/plugins/region_map/public/plugin.ts delete mode 100644 src/plugins/region_map/public/region_map_fn.test.ts delete mode 100644 src/plugins/region_map/public/region_map_fn.ts delete mode 100644 src/plugins/region_map/public/region_map_renderer.tsx delete mode 100644 src/plugins/region_map/public/region_map_type.ts delete mode 100644 src/plugins/region_map/public/region_map_types.ts delete mode 100644 src/plugins/region_map/public/region_map_visualization.js delete mode 100644 src/plugins/region_map/public/region_map_visualization.scss delete mode 100644 src/plugins/region_map/public/region_map_visualization_component.tsx delete mode 100644 src/plugins/region_map/public/to_ast.ts delete mode 100644 src/plugins/region_map/public/tooltip_formatter.js delete mode 100644 src/plugins/region_map/public/util.ts delete mode 100644 src/plugins/region_map/server/index.ts delete mode 100644 src/plugins/region_map/server/ui_settings.ts delete mode 100644 src/plugins/region_map/tsconfig.json delete mode 100644 src/plugins/tile_map/README.md delete mode 100644 src/plugins/tile_map/jest.config.js delete mode 100644 src/plugins/tile_map/kibana.json delete mode 100644 src/plugins/tile_map/package.json delete mode 100644 src/plugins/tile_map/public/__snapshots__/tile_map_fn.test.ts.snap delete mode 100644 src/plugins/tile_map/public/components/collections.ts delete mode 100644 src/plugins/tile_map/public/components/index.tsx delete mode 100644 src/plugins/tile_map/public/components/tile_map_options.tsx delete mode 100644 src/plugins/tile_map/public/css_filters.js delete mode 100644 src/plugins/tile_map/public/geohash_layer.js delete mode 100644 src/plugins/tile_map/public/get_deprecation_message.tsx delete mode 100644 src/plugins/tile_map/public/index.ts delete mode 100644 src/plugins/tile_map/public/markers/geohash_grid.js delete mode 100644 src/plugins/tile_map/public/markers/heatmap.js delete mode 100644 src/plugins/tile_map/public/markers/scaled_circles.js delete mode 100644 src/plugins/tile_map/public/markers/shaded_circles.js delete mode 100644 src/plugins/tile_map/public/plugin.ts delete mode 100644 src/plugins/tile_map/public/services.ts delete mode 100644 src/plugins/tile_map/public/tile_map_fn.test.ts delete mode 100644 src/plugins/tile_map/public/tile_map_fn.ts delete mode 100644 src/plugins/tile_map/public/tile_map_renderer.tsx delete mode 100644 src/plugins/tile_map/public/tile_map_type.ts delete mode 100644 src/plugins/tile_map/public/tile_map_visualization.js delete mode 100644 src/plugins/tile_map/public/tile_map_visualization.scss delete mode 100644 src/plugins/tile_map/public/tile_map_visualization_component.tsx delete mode 100644 src/plugins/tile_map/public/to_ast.ts delete mode 100644 src/plugins/tile_map/public/tooltip_formatter.js delete mode 100644 src/plugins/tile_map/public/types.ts delete mode 100644 src/plugins/tile_map/public/utils/convert_to_geojson.ts delete mode 100644 src/plugins/tile_map/public/utils/decode_geo_hash.test.ts delete mode 100644 src/plugins/tile_map/public/utils/decode_geo_hash.ts delete mode 100644 src/plugins/tile_map/public/utils/grid_dimensions.ts delete mode 100644 src/plugins/tile_map/public/utils/index.ts delete mode 100644 src/plugins/tile_map/public/utils/map_types.ts delete mode 100644 src/plugins/tile_map/server/index.ts delete mode 100644 src/plugins/tile_map/tsconfig.json delete mode 100644 test/functional/apps/visualize/_region_map.ts delete mode 100644 test/functional/apps/visualize/_tile_map.ts delete mode 100644 test/functional/page_objects/tile_map_page.ts delete mode 100644 test/interpreter_functional/screenshots/baseline/partial_test_3.png delete mode 100644 test/interpreter_functional/snapshots/baseline/partial_test_3.json delete mode 100644 test/interpreter_functional/snapshots/session/partial_test_3.json create mode 100644 x-pack/plugins/maps/public/embeddable/_index.scss create mode 100644 x-pack/plugins/maps/public/embeddable/map_component.tsx create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/index.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/index.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_fn.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_vis_type.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/to_ast.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/types.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/region_map/utils.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/index.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_fn.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_vis_type.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/to_ast.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/types.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/tile_map/utils.ts create mode 100644 x-pack/plugins/maps/public/legacy_visualizations/view_in_maps.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d116b1d3a41fc3..b47c3b09cce308 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -178,12 +178,8 @@ /x-pack/test/visual_regression/tests/maps/index.js @elastic/kibana-gis /x-pack/plugins/stack_alerts/server/alert_types/geo_containment @elastic/kibana-gis /x-pack/plugins/stack_alerts/public/alert_types/geo_containment @elastic/kibana-gis -#CC# /src/plugins/maps_legacy/ @elastic/kibana-gis -/src/plugins/maps_legacy/ @elastic/kibana-gis #CC# /x-pack/plugins/file_upload @elastic/kibana-gis /x-pack/plugins/file_upload @elastic/kibana-gis -/src/plugins/tile_map/ @elastic/kibana-gis -/src/plugins/region_map/ @elastic/kibana-gis /packages/kbn-mapbox-gl @elastic/kibana-gis # Operations diff --git a/.i18nrc.json b/.i18nrc.json index 3960831a3a4fa6..d19226e6b6f8cb 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -33,7 +33,6 @@ "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", "lists": "packages/kbn-securitysolution-list-utils/src", "management": ["src/legacy/core_plugins/management", "src/plugins/management"], - "maps_legacy": "src/plugins/maps_legacy", "monaco": "packages/kbn-monaco/src", "esQuery": "packages/kbn-es-query/src", "presentationUtil": "src/plugins/presentation_util", @@ -49,14 +48,12 @@ "kibana_utils": "src/plugins/kibana_utils", "navigation": "src/plugins/navigation", "newsfeed": "src/plugins/newsfeed", - "regionMap": "src/plugins/region_map", "savedObjects": "src/plugins/saved_objects", "savedObjectsManagement": "src/plugins/saved_objects_management", "security": "src/plugins/security_oss", "server": "src/legacy/server", "statusPage": "src/legacy/core_plugins/status_page", "telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"], - "tileMap": "src/plugins/tile_map", "timelion": ["src/plugins/timelion", "src/plugins/vis_type_timelion"], "uiActions": "src/plugins/ui_actions", "visDefaultEditor": "src/plugins/vis_default_editor", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index dc410f2e5f2a56..6431d85ac1a510 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -182,10 +182,6 @@ management section itself. |Configuration of kibana-wide EMS settings and some higher level utilities. -|{kib-repo}blob/{branch}/src/plugins/maps_legacy/README.md[mapsLegacy] -|Internal objects used by the Coordinate, Region, and Vega visualizations. - - |{kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] |The navigation plugins exports the TopNavMenu component. It also provides a stateful version of it on the start contract. @@ -200,10 +196,6 @@ Content is fetched from the remote (https://feeds.elastic.co and https://feeds-s |The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). -|{kib-repo}blob/{branch}/src/plugins/region_map/README.md[regionMap] -|Create choropleth maps. Display the results of a term-aggregation as e.g. countries, zip-codes, states. - - |{kib-repo}blob/{branch}/src/plugins/saved_objects/README.md[savedObjects] |NOTE: This plugin is deprecated and will be removed in 8.0. See https://github.com/elastic/kibana/issues/46435 for more information. @@ -247,10 +239,6 @@ generating deep links to other apps, and creating short URLs. |This plugin adds the Advanced Settings section for the Usage and Security Data collection (aka Telemetry). -|{kib-repo}blob/{branch}/src/plugins/tile_map/README.md[tileMap] -|Create a coordinate map. Display the results of a geohash_tile aggregation as bubbles, rectangles, or heatmap color blobs. - - |{kib-repo}blob/{branch}/src/plugins/timelion/README.md[timelion] |Contains the deprecated timelion application. For the timelion visualization, which also contains the timelion APIs and backend, look at the vis_type_timelion plugin. diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 23f79c1bbb4805..a5bdad16fa98f7 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -534,17 +534,6 @@ of the chart. Use numbers between 0 and 1. The lower the number, the more the hi [[visualization-heatmap-maxbuckets]]`visualization:heatmap:maxBuckets`:: The maximum number of buckets a datasource can return. High numbers can have a negative impact on your browser rendering performance. -[[visualization-regionmap-showwarnings]]`visualization:regionmap:showWarnings`:: -Shows a warning in a region map when terms cannot be joined to a shape. - -[[visualization-tilemap-wmsdefaults]]`visualization:tileMap:WMSdefaults`:: -The default properties for the WMS map server supported in the coordinate map. - -[[visualization-tilemap-maxprecision]]`visualization:tileMap:maxPrecision`:: -The maximum geoHash precision displayed in tile maps. 7 is high, 10 is very high, -and 12 is the maximum. For more information, refer to -{ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[Cell dimensions at the equator]. - [[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`:: **The legacy XY charts are deprecated and will not be supported as of 7.16.** The visualize editor uses a new XY charts library with improved performance, color palettes, fill capacity, and more. Enable this option if you prefer to use the legacy charts library. @@ -563,4 +552,4 @@ only production-ready visualizations are available to users. [horizontal] [[telemetry-enabled-advanced-setting]]`telemetry:enabled`:: When enabled, helps improve the Elastic Stack by providing usage statistics for -basic features. This data will not be shared outside of Elastic. +basic features. This data will not be shared outside of Elastic. \ No newline at end of file diff --git a/package.json b/package.json index 1fcda924722bf8..77836c8e29c984 100644 --- a/package.json +++ b/package.json @@ -280,10 +280,6 @@ "jsonwebtoken": "^8.5.1", "jsts": "^1.6.2", "kea": "^2.4.2", - "leaflet": "1.5.1", - "leaflet-draw": "0.4.14", - "leaflet-responsive-popup": "0.6.4", - "leaflet.heat": "0.2.0", "less": "npm:@elastic/less@2.7.3-kibana", "load-json-file": "^6.2.0", "loader-utils": "^1.2.3", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 48130a7bfcf5b0..b11458d6539e85 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -50,15 +50,13 @@ pageLoadAssetSize: lists: 22900 logstash: 53548 management: 46112 - maps: 80000 - mapsLegacy: 87859 + maps: 90000 ml: 82187 monitoring: 80000 navigation: 37269 newsfeed: 42228 observability: 89709 painlessLab: 179748 - regionMap: 66098 remoteClusters: 51327 reporting: 183418 rollup: 97204 @@ -75,7 +73,6 @@ pageLoadAssetSize: spaces: 57868 telemetry: 51957 telemetryManagementSection: 38586 - tileMap: 65337 timelion: 29920 transform: 41007 triggersActionsUi: 100000 diff --git a/src/core/server/ui_settings/saved_objects/migrations.test.ts b/src/core/server/ui_settings/saved_objects/migrations.test.ts index cb10f9c7fd9814..c454338f44c79f 100644 --- a/src/core/server/ui_settings/saved_objects/migrations.test.ts +++ b/src/core/server/ui_settings/saved_objects/migrations.test.ts @@ -128,3 +128,38 @@ describe('ui_settings 7.13.0 migrations', () => { }); }); }); + +describe('ui_settings 8.0.0 migrations', () => { + const migration = migrations['8.0.0']; + + test('returns doc on empty object', () => { + expect(migration({} as SavedObjectUnsanitizedDoc)).toEqual({ + references: [], + }); + }); + test('removes ui_settings from deleted region_map and tile_map plugins', () => { + const doc = { + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + 'visualization:regionmap:showWarnings': false, + 'visualization:tileMap:WMSdefaults': '{}', + 'visualization:tileMap:maxPrecision': 10, + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }; + expect(migration(doc)).toEqual({ + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }); + }); +}); diff --git a/src/core/server/ui_settings/saved_objects/migrations.ts b/src/core/server/ui_settings/saved_objects/migrations.ts index b187c5f86dab02..e5d1a6bd1aa258 100644 --- a/src/core/server/ui_settings/saved_objects/migrations.ts +++ b/src/core/server/ui_settings/saved_objects/migrations.ts @@ -75,4 +75,27 @@ export const migrations = { }), references: doc.references || [], }), + '8.0.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({ + ...doc, + ...(doc.attributes && { + // owner: Team:Geo + attributes: Object.keys(doc.attributes).reduce( + (acc, key) => + [ + 'visualization:regionmap:showWarnings', + 'visualization:tileMap:WMSdefaults', + 'visualization:tileMap:maxPrecision', + ].includes(key) + ? { + ...acc, + } + : { + ...acc, + [key]: doc.attributes[key], + }, + {} + ), + }), + references: doc.references || [], + }), }; diff --git a/src/plugins/maps_legacy/README.md b/src/plugins/maps_legacy/README.md deleted file mode 100644 index 4a870e4f7492d8..00000000000000 --- a/src/plugins/maps_legacy/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Maps legacy - -Internal objects used by the Coordinate, Region, and Vega visualizations. - -It exports the default Leaflet-based map and exposes the connection to the Elastic Maps service. - -This plugin is targeted for removal in 8.0. \ No newline at end of file diff --git a/src/plugins/maps_legacy/config.ts b/src/plugins/maps_legacy/config.ts deleted file mode 100644 index 41387a07d27cb6..00000000000000 --- a/src/plugins/maps_legacy/config.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { schema, TypeOf } from '@kbn/config-schema'; - -export const configSchema = schema.object({}); - -export type MapsLegacyConfig = TypeOf; diff --git a/src/plugins/maps_legacy/jest.config.js b/src/plugins/maps_legacy/jest.config.js deleted file mode 100644 index cbdcdf0905777f..00000000000000 --- a/src/plugins/maps_legacy/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/src/plugins/maps_legacy'], -}; diff --git a/src/plugins/maps_legacy/kibana.json b/src/plugins/maps_legacy/kibana.json deleted file mode 100644 index fde5ad7b7adf5d..00000000000000 --- a/src/plugins/maps_legacy/kibana.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "mapsLegacy", - "owner": { - "name": "GIS", - "githubTeam": "kibana-gis" - }, - "version": "8.0.0", - "kibanaVersion": "kibana", - "ui": true, - "server": true, - "requiredPlugins": ["mapsEms"], - "requiredBundles": ["visDefaultEditor", "mapsEms"] -} diff --git a/src/plugins/maps_legacy/public/common/types.ts b/src/plugins/maps_legacy/public/common/types.ts deleted file mode 100644 index 8ff1753b56c312..00000000000000 --- a/src/plugins/maps_legacy/public/common/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { TmsLayer } from '../../../maps_ems/public'; - -export interface WMSOptions { - selectedTmsLayer?: TmsLayer; - enabled: boolean; - url?: string; - options: { - version?: string; - layers?: string; - format: string; - transparent: boolean; - attribution?: string; - styles?: string; - }; -} diff --git a/src/plugins/maps_legacy/public/components/legacy_map_deprecation_message.tsx b/src/plugins/maps_legacy/public/components/legacy_map_deprecation_message.tsx deleted file mode 100644 index 513a3562c3d8f3..00000000000000 --- a/src/plugins/maps_legacy/public/components/legacy_map_deprecation_message.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -interface Props { - isMapsAvailable: boolean; - onClick: (e: React.MouseEvent) => Promise; - visualizationLabel: string; -} - -export function LegacyMapDeprecationMessage(props: Props) { - const getMapsMessage = !props.isMapsAvailable ? ( - - default distribution - - ), - }} - /> - ) : null; - - const button = props.isMapsAvailable ? ( -
- - - -
- ) : null; - - return ( - -

- -

- {button} -
- ); -} diff --git a/src/plugins/maps_legacy/public/components/wms_internal_options.tsx b/src/plugins/maps_legacy/public/components/wms_internal_options.tsx deleted file mode 100644 index d666a97489b629..00000000000000 --- a/src/plugins/maps_legacy/public/components/wms_internal_options.tsx +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { TextInputOption } from '../../../vis_default_editor/public'; -import { WMSOptions } from '../common/types'; - -interface WmsInternalOptions { - wms: WMSOptions; - setValue: (paramName: T, value: WMSOptions[T]) => void; -} - -function WmsInternalOptions({ wms, setValue }: WmsInternalOptions) { - const wmsLink = ( - - - - ); - const footnoteText = ( - <> - - - - ); - const footnote = ( - -

{footnoteText}

-
- ); - - const setOptions = ( - paramName: T, - value: WMSOptions['options'][T] - ) => - setValue('options', { - ...wms.options, - [paramName]: value, - }); - - return ( - <> - - - - - - - - - - - } - helpText={ - <> - - {footnote} - - } - paramName="url" - value={wms.url} - setValue={setValue} - /> - - - - - - } - helpText={ - <> - - {footnote} - - } - paramName="layers" - value={wms.options.layers} - setValue={setOptions} - /> - - - - - - } - helpText={ - <> - - {footnote} - - } - paramName="version" - value={wms.options.version} - setValue={setOptions} - /> - - - - - - } - helpText={ - <> - - {footnote} - - } - paramName="format" - value={wms.options.format} - setValue={setOptions} - /> - - - } - helpText={ - - } - paramName="attribution" - value={wms.options.attribution} - setValue={setOptions} - /> - - - - - - } - helpText={ - <> - - {footnote} - - } - paramName="styles" - value={wms.options.styles} - setValue={setOptions} - /> - - - - - - ); -} - -export { WmsInternalOptions }; diff --git a/src/plugins/maps_legacy/public/components/wms_options.tsx b/src/plugins/maps_legacy/public/components/wms_options.tsx deleted file mode 100644 index 8f63d205406e9d..00000000000000 --- a/src/plugins/maps_legacy/public/components/wms_options.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useMemo } from 'react'; -import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { TmsLayer } from '../../../maps_ems/public'; -import { SelectOption, SwitchOption } from '../../../vis_default_editor/public'; -import { WmsInternalOptions } from './wms_internal_options'; -import { WMSOptions } from '../common/types'; - -interface Props { - stateParams: K; - setValue: (title: 'wms', options: WMSOptions) => void; - tmsLayers: TmsLayer[]; -} - -const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id, value: id }); - -function WmsOptions({ stateParams, setValue, tmsLayers }: Props) { - const { wms } = stateParams; - const tmsLayerOptions = useMemo(() => tmsLayers.map(mapLayerForOption), [tmsLayers]); - - const setWmsOption = (paramName: T, value: WMSOptions[T]) => - setValue('wms', { - ...wms, - [paramName]: value, - }); - - const selectTmsLayer = (id: string) => { - const layer = tmsLayers.find((l: TmsLayer) => l.id === id); - if (layer) { - setWmsOption('selectedTmsLayer', layer); - } - }; - - return ( - - -

- -

-
- - - - - {!wms.enabled && ( - <> - - selectTmsLayer(value)} - /> - - )} - - {wms.enabled && } -
- ); -} - -export { WmsOptions }; diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts deleted file mode 100644 index c21aabcf743b9d..00000000000000 --- a/src/plugins/maps_legacy/public/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PluginInitializerContext } from 'kibana/public'; -import { MapsLegacyPlugin } from './plugin'; -import * as colorUtil from './map/color_util'; -import { KibanaMapLayer } from './map/kibana_map_layer'; -import { mapTooltipProvider } from './tooltip_provider'; - -import './map/index.scss'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new MapsLegacyPlugin(initializerContext); -} - -/** @public */ -export { colorUtil, KibanaMapLayer, mapTooltipProvider }; - -export { WMSOptions } from './common/types'; -export { WmsOptions } from './components/wms_options'; -export { LegacyMapDeprecationMessage } from './components/legacy_map_deprecation_message'; - -export { lazyLoadMapsLegacyModules } from './lazy_load_bundle'; - -export type MapsLegacyPluginSetup = ReturnType; -export type MapsLegacyPluginStart = ReturnType; diff --git a/src/plugins/maps_legacy/public/kibana_services.ts b/src/plugins/maps_legacy/public/kibana_services.ts deleted file mode 100644 index 1cf02ee06db88e..00000000000000 --- a/src/plugins/maps_legacy/public/kibana_services.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { IUiSettingsClient, ToastsSetup } from 'kibana/public'; -import type { MapsEmsConfig, IServiceSettings } from '../../maps_ems/public'; - -let toast: ToastsSetup; -export const setToasts = (notificationToast: ToastsSetup) => (toast = notificationToast); -export const getToasts = () => toast; - -let uiSettings: IUiSettingsClient; -export const setUiSettings = (coreUiSettings: IUiSettingsClient) => (uiSettings = coreUiSettings); -export const getUiSettings = () => uiSettings; - -let mapsEmsConfig: MapsEmsConfig; -export const setMapsEmsConfig = (config: MapsEmsConfig) => (mapsEmsConfig = config); -export const getEmsTileLayerId = () => mapsEmsConfig.emsTileLayerId; - -let getServiceSettingsFunction: () => Promise; -export const setGetServiceSettings = (getSS: () => Promise) => - (getServiceSettingsFunction = getSS); -export const getServiceSettings = async (): Promise => { - return await getServiceSettingsFunction(); -}; diff --git a/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts b/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts deleted file mode 100644 index b1509c4effa7a0..00000000000000 --- a/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -let loadModulesPromise: Promise; - -interface LazyLoadedMapsLegacyModules { - KibanaMap: unknown; - L: unknown; -} - -export async function lazyLoadMapsLegacyModules(): Promise { - if (typeof loadModulesPromise !== 'undefined') { - return loadModulesPromise; - } - - loadModulesPromise = new Promise(async (resolve) => { - const { KibanaMap, L } = await import('./lazy'); - - resolve({ - KibanaMap, - L, - }); - }); - return loadModulesPromise; -} diff --git a/src/plugins/maps_legacy/public/lazy_load_bundle/lazy/index.ts b/src/plugins/maps_legacy/public/lazy_load_bundle/lazy/index.ts deleted file mode 100644 index 5fb53c1a4f524a..00000000000000 --- a/src/plugins/maps_legacy/public/lazy_load_bundle/lazy/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// @ts-expect-error -export { KibanaMap } from '../../map/kibana_map'; -// @ts-expect-error -export { L } from '../../leaflet'; diff --git a/src/plugins/maps_legacy/public/leaflet.js b/src/plugins/maps_legacy/public/leaflet.js deleted file mode 100644 index fd02f83d72823c..00000000000000 --- a/src/plugins/maps_legacy/public/leaflet.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -if (!window.hasOwnProperty('L')) { - require('leaflet/dist/leaflet.css'); - window.L = require('leaflet/dist/leaflet.js'); - window.L.Browser.touch = false; - window.L.Browser.pointer = false; - - require('leaflet.heat/dist/leaflet-heat.js'); - require('leaflet-draw/dist/leaflet.draw.css'); - require('leaflet-draw/dist/leaflet.draw.js'); - require('leaflet-responsive-popup/leaflet.responsive.popup.css'); - require('leaflet-responsive-popup/leaflet.responsive.popup.js'); -} - -export const L = window.L; diff --git a/src/plugins/maps_legacy/public/map/_leaflet_overrides.scss b/src/plugins/maps_legacy/public/map/_leaflet_overrides.scss deleted file mode 100644 index c688a8c9b518cb..00000000000000 --- a/src/plugins/maps_legacy/public/map/_leaflet_overrides.scss +++ /dev/null @@ -1,158 +0,0 @@ -// stylelint-disable selector-no-qualifying-type -// SASSTODO: Create these tooltip variables in EUI -// And/Or create a tooltip mixin -$tempEUITooltipBackground: tintOrShade($euiColorFullShade, 25%, 90%); -$tempEUITooltipText: $euiColorGhost; - -// Converted leaflet icon sprite into background svg for custom coloring (dark mode) -$visMapLeafletSprite: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 60' height='60' width='600'%3E%3Cg fill='#{hexToRGB($euiTextColor)}'%3E%3Cg%3E%3Cpath d='M18 36v6h6v-6h-6zm4 4h-2v-2h2v2z'/%3E%3Cpath d='M36 18v6h6v-6h-6zm4 4h-2v-2h2v2z'/%3E%3Cpath d='M23.142 39.145l-2.285-2.29 16-15.998 2.285 2.285z'/%3E%3C/g%3E%3Cpath d='M100 24.565l-2.096 14.83L83.07 42 76 28.773 86.463 18z'/%3E%3Cpath d='M140 20h20v20h-20z'/%3E%3Cpath d='M221 30c0 6.078-4.926 11-11 11s-11-4.922-11-11c0-6.074 4.926-11 11-11s11 4.926 11 11z'/%3E%3Cpath d='M270,19c-4.971,0-9,4.029-9,9c0,4.971,5.001,12,9,14c4.001-2,9-9.029,9-14C279,23.029,274.971,19,270,19z M270,31.5c-2.484,0-4.5-2.014-4.5-4.5c0-2.484,2.016-4.5,4.5-4.5c2.485,0,4.5,2.016,4.5,4.5C274.5,29.486,272.485,31.5,270,31.5z'/%3E%3Cg%3E%3Cpath d='M337,30.156v0.407v5.604c0,1.658-1.344,3-3,3h-10c-1.655,0-3-1.342-3-3v-10c0-1.657,1.345-3,3-3h6.345 l3.19-3.17H324c-3.313,0-6,2.687-6,6v10c0,3.313,2.687,6,6,6h10c3.314,0,6-2.687,6-6v-8.809L337,30.156'/%3E%3Cpath d='M338.72 24.637l-8.892 8.892H327V30.7l8.89-8.89z'/%3E%3Cpath d='M338.697 17.826h4v4h-4z' transform='rotate(-134.99 340.703 19.817)'/%3E%3C/g%3E%3Cg%3E%3Cpath d='M381 42h18V24h-18v18zm14-16h2v14h-2V26zm-4 0h2v14h-2V26zm-4 0h2v14h-2V26zm-4 0h2v14h-2V26z'/%3E%3Cpath d='M395 20v-4h-10v4h-6v2h22v-2h-6zm-2 0h-6v-2h6v2z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A"; - -.leaflet-touch .leaflet-bar, -.leaflet-draw-actions { - @include euiBottomShadowMedium($color: $euiShadowColorLarge, $opacity: .2); - border: none; -} - -.leaflet-container { - background: $euiColorEmptyShade; - - //the heatmap layer plugin logs an error to the console when the map is in a 0-sized container - min-width: 1px !important; - min-height: 1px !important; -} - -.leaflet-clickable { - &:hover { - stroke-width: $euiSizeS; - stroke-opacity: .8; - } -} - -/** - * 1. Since Leaflet is an external library, we also have to provide EUI variables - * to non-override colors for darkmode. - */ - -.leaflet-draw-actions, -.leaflet-control { - a { - background-color: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightShade); /* 1 */ - border-color: lightOrDarkTheme($euiColorLightShade, $euiColorMediumShade) !important; /* 1 */ - color: $euiTextColor !important; /* 1 */ - - &:hover { - background-color: $euiColorLightestShade; - } - } -} - -.leaflet-touch .leaflet-bar a:first-child { - border-top-left-radius: $euiBorderRadius; - border-top-right-radius: $euiBorderRadius; -} - -.leaflet-touch .leaflet-bar a:last-child { - border-bottom-left-radius: $euiBorderRadius; - border-bottom-right-radius: $euiBorderRadius; -} - -.leaflet-retina .leaflet-draw-toolbar a { - background-image: url($visMapLeafletSprite); /* 1 */ -} - -.leaflet-control-layers-expanded { - padding: 0; - margin: 0; - @include fontSize(11px); - font-family: $euiFontFamily; - font-weight: $euiFontWeightMedium; - line-height: $euiLineHeight; - - label { - font-weight: $euiFontWeightMedium; - margin: 0; - padding: 0; - } -} - -/* over-rides leaflet popup styles to look like kibana tooltip */ -.leaflet-popup-content-wrapper { - margin: 0; - padding: 0; - background: $tempEUITooltipBackground; - color: $tempEUITooltipText; - border-radius: $euiBorderRadius !important; // Override all positions the popup might be at -} - -.leaflet-popup { - pointer-events: none; -} - -.leaflet-popup-content { - margin: 0; - @include euiFontSizeS; - font-weight: $euiFontWeightRegular; - word-wrap: break-word; - overflow: hidden; - pointer-events: none; - - > * { - margin: $euiSizeS $euiSizeS 0; - } - - > :last-child { - margin-bottom: $euiSizeS; - } - - table { - td,th { - padding: $euiSizeXS; - } - } -} - -.leaflet-popup-tip-container, -.leaflet-popup-close-button, -.leaflet-draw-tooltip { - display: none !important; -} - -.leaflet-container .leaflet-control-attribution { - background-color: transparentize($euiColorEmptyShade, .7); - color: $euiColorDarkShade; - - // attributions are appended in blocks of

tags, this will allow them to display in one line - p { - display: inline; - } -} - -.leaflet-touch .leaflet-control-zoom-in, -.leaflet-touch .leaflet-control-zoom-out { - text-indent: -10000px; - background-repeat: no-repeat; - background-position: center; -} - -// Custom SVG as background for zoom controls based off of EUI glyphs plusInCircleFilled and minusInCircleFilled -.leaflet-touch .leaflet-control-zoom-in { - background-image: url("data:image/svg+xml,%0A%3Csvg width='15px' height='15px' viewBox='0 0 15 15' version='1.1' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='#{hexToRGB($euiTextColor)}' d='M8,7 L8,3.5 C8,3.22385763 7.77614237,3 7.5,3 C7.22385763,3 7,3.22385763 7,3.5 L7,7 L3.5,7 C3.22385763,7 3,7.22385763 3,7.5 C3,7.77614237 3.22385763,8 3.5,8 L7,8 L7,11.5 C7,11.7761424 7.22385763,12 7.5,12 C7.77614237,12 8,11.7761424 8,11.5 L8,8 L11.5,8 C11.7761424,8 12,7.77614237 12,7.5 C12,7.22385763 11.7761424,7 11.5,7 L8,7 Z M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z' /%3E%3C/svg%3E"); -} - -.leaflet-touch .leaflet-control-zoom-out { - background-image: url("data:image/svg+xml,%0A%3Csvg width='15px' height='15px' viewBox='0 0 15 15' version='1.1' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='#{hexToRGB($euiTextColor)}' d='M7.5,0 C11.6355882,0 15,3.36441176 15,7.5 C15,11.6355882 11.6355882,15 7.5,15 C3.36441176,15 0,11.6355882 0,7.5 C0,3.36441176 3.36441176,0 7.5,0 Z M3.5,7 C3.22385763,7 3,7.22385763 3,7.5 C3,7.77614237 3.22385763,8 3.5,8 L11.5,8 C11.7761424,8 12,7.77614237 12,7.5 C12,7.22385763 11.7761424,7 11.5,7 L3.5,7 Z' /%3E%3C/svg%3E"); -} - -// Filter to desaturate mapquest tiles - -img.leaflet-tile { - @if (lightness($euiTextColor) < 50) { - filter: brightness(1.03) grayscale(.73); - } @else { - filter: invert(1) brightness(1.75) grayscale(1); - } -} - -img.leaflet-tile.filters-off { - filter: none; -} diff --git a/src/plugins/maps_legacy/public/map/_legend.scss b/src/plugins/maps_legacy/public/map/_legend.scss deleted file mode 100644 index 27016840cfabf7..00000000000000 --- a/src/plugins/maps_legacy/public/map/_legend.scss +++ /dev/null @@ -1,33 +0,0 @@ -.visMapLegend { - @include fontSize(11px); - @include euiBottomShadowMedium($color: $euiShadowColorLarge); - font-family: $euiFontFamily; - font-weight: $euiFontWeightMedium; - line-height: $euiLineHeight; - color: $euiColorDarkShade; - padding: $euiSizeS; - background: transparentize($euiColorEmptyShade, .2); - border-radius: $euiBorderRadius; - - i { - @include size($euiSizeS + 2px); - display: inline-block; - margin: 3px $euiSizeXS 0 0; - border-radius: 50%; - border: 1px solid $euiColorDarkShade; - background: $euiColorDarkShade; - } -} - -.visMapLegend__title { - font-weight: $euiFontWeightBold; -} - -// Wrapper/Position - -// top left needs some more styles -.leaflet-top.leaflet-left .visMapLegend__wrapper { - position: absolute; - left: $euiSizeXXL; - white-space: nowrap; -} diff --git a/src/plugins/maps_legacy/public/map/base_maps_visualization.js b/src/plugins/maps_legacy/public/map/base_maps_visualization.js deleted file mode 100644 index a261bcf6edd809..00000000000000 --- a/src/plugins/maps_legacy/public/map/base_maps_visualization.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import * as Rx from 'rxjs'; -import { filter, first } from 'rxjs/operators'; -import { - getEmsTileLayerId, - getUiSettings, - getToasts, - getServiceSettings, -} from '../kibana_services'; -import { lazyLoadMapsLegacyModules } from '../lazy_load_bundle'; - -const WMS_MINZOOM = 0; -const WMS_MAXZOOM = 22; //increase this to 22. Better for WMS - -export function BaseMapsVisualizationProvider() { - /** - * Abstract base class for a visualization consisting of a map with a single baselayer. - * @class BaseMapsVisualization - * @constructor - */ - return class BaseMapsVisualization { - constructor(element, handlers, initialVisParams) { - this.handlers = handlers; - this._params = initialVisParams; - this._container = element; - this._kibanaMap = null; - this._chartData = null; //reference to data currently on the map. - this._baseLayerDirty = true; - this._mapIsLoaded = this._makeKibanaMap(); - } - - isLoaded() { - return this._mapIsLoaded; - } - - destroy() { - if (this._kibanaMap) { - this._kibanaMap.destroy(); - this._kibanaMap = null; - } - } - - /** - * Implementation of Visualization#render. - * Child-classes can extend this method if the render-complete function requires more time until rendering has completed. - * @param esResponse - * @param status - * @return {Promise} - */ - async render(esResponse = this._esResponse, visParams = this._params) { - await this._mapIsLoaded; - - if (!this._kibanaMap) { - //the visualization has been destroyed; - return; - } - - this.resize(); - this._params = visParams; - await this._updateParams(); - - if (this._hasESResponseChanged(esResponse)) { - this._esResponse = esResponse; - await this._updateData(esResponse); - } - this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState); - - await this._whenBaseLayerIsLoaded(); - } - - resize() { - this._kibanaMap?.resize(); - } - - /** - * Creates an instance of a kibana-map with a single baselayer and assigns it to the this._kibanaMap property. - * Clients can override this method to customize the initialization. - * @private - */ - async _makeKibanaMap() { - const options = {}; - const zoomFromUiState = parseInt(this.handlers.uiState?.get('mapZoom')); - const centerFromUIState = this.handlers.uiState?.get('mapCenter'); - const { mapZoom, mapCenter } = this._getMapsParams(); - options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : mapZoom; - options.center = centerFromUIState ? centerFromUIState : mapCenter; - - const modules = await lazyLoadMapsLegacyModules(); - this._kibanaMap = new modules.KibanaMap(this._container, options); - this._kibanaMap.setMinZoom(WMS_MINZOOM); //use a default - this._kibanaMap.setMaxZoom(WMS_MAXZOOM); //use a default - - this._kibanaMap.addLegendControl(); - this._kibanaMap.addFitControl(); - this._kibanaMap.persistUiStateForVisualization(this.handlers.uiState); - - this._kibanaMap.on('baseLayer:loaded', () => { - this._baseLayerDirty = false; - }); - this._kibanaMap.on('baseLayer:loading', () => { - this._baseLayerDirty = true; - }); - await this._updateBaseLayer(); - } - - _tmsConfigured() { - const { wms } = this._getMapsParams(); - const hasTmsBaseLayer = wms && !!wms.selectedTmsLayer; - - return hasTmsBaseLayer; - } - - _wmsConfigured() { - const { wms } = this._getMapsParams(); - const hasWmsBaseLayer = wms && !!wms.enabled; - - return hasWmsBaseLayer; - } - - async _updateBaseLayer() { - const emsTileLayerId = getEmsTileLayerId(); - - if (!this._kibanaMap) { - return; - } - - const mapParams = this._getMapsParams(); - if (!this._tmsConfigured()) { - try { - const serviceSettings = await getServiceSettings(); - const tmsServices = await serviceSettings.getTMSServices(); - const userConfiguredTmsLayer = tmsServices[0]; - const initBasemapLayer = userConfiguredTmsLayer - ? userConfiguredTmsLayer - : tmsServices.find((s) => s.id === emsTileLayerId.bright); - if (initBasemapLayer) { - this._setTmsLayer(initBasemapLayer); - } - } catch (e) { - getToasts().addWarning(e.message); - return; - } - return; - } - - try { - if (this._wmsConfigured()) { - if (WMS_MINZOOM > this._kibanaMap.getMaxZoomLevel()) { - this._kibanaMap.setMinZoom(WMS_MINZOOM); - this._kibanaMap.setMaxZoom(WMS_MAXZOOM); - } - - this._kibanaMap.setBaseLayer({ - baseLayerType: 'wms', - options: { - minZoom: WMS_MINZOOM, - maxZoom: WMS_MAXZOOM, - url: mapParams.wms.url, - ...mapParams.wms.options, - }, - }); - } else if (this._tmsConfigured()) { - const selectedTmsLayer = mapParams.wms.selectedTmsLayer; - this._setTmsLayer(selectedTmsLayer); - } - } catch (tmsLoadingError) { - getToasts().addWarning(tmsLoadingError.message); - } - } - - async _setTmsLayer(tmsLayer) { - this._kibanaMap.setMinZoom(tmsLayer.minZoom); - this._kibanaMap.setMaxZoom(tmsLayer.maxZoom); - if (this._kibanaMap.getZoomLevel() > tmsLayer.maxZoom) { - this._kibanaMap.setZoomLevel(tmsLayer.maxZoom); - } - let isDesaturated = this._getMapsParams().isDesaturated; - if (typeof isDesaturated !== 'boolean') { - isDesaturated = true; - } - const isDarkMode = getUiSettings().get('theme:darkMode'); - const serviceSettings = await getServiceSettings(); - const meta = await serviceSettings.getAttributesForTMSLayer( - tmsLayer, - isDesaturated, - isDarkMode - ); - const options = { ...tmsLayer }; - delete options.id; - delete options.subdomains; - this._kibanaMap.setBaseLayer({ - baseLayerType: 'tms', - options: { ...options, ...meta }, - }); - } - - async _updateData() { - throw new Error( - i18n.translate('maps_legacy.baseMapsVisualization.childShouldImplementMethodErrorMessage', { - defaultMessage: 'Child should implement this method to respond to data-update', - }) - ); - } - - _hasESResponseChanged(data) { - return this._esResponse !== data; - } - - /** - * called on options change (vis.params change) - */ - async _updateParams() { - const mapParams = this._getMapsParams(); - await this._updateBaseLayer(); - this._kibanaMap.setLegendPosition(mapParams.legendPosition); - this._kibanaMap.setShowTooltip(mapParams.addTooltip); - this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState); - } - - _getMapsParams() { - return this._params; - } - - _whenBaseLayerIsLoaded() { - if (!this._tmsConfigured()) { - return true; - } - - const maxTimeForBaseLayer = 10000; - const interval$ = Rx.interval(10).pipe(filter(() => !this._baseLayerDirty)); - const timer$ = Rx.timer(maxTimeForBaseLayer); - - return Rx.race(interval$, timer$).pipe(first()).toPromise(); - } - }; -} diff --git a/src/plugins/maps_legacy/public/map/color_util.d.ts b/src/plugins/maps_legacy/public/map/color_util.d.ts deleted file mode 100644 index 8ab753e5295182..00000000000000 --- a/src/plugins/maps_legacy/public/map/color_util.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function getLegendColors(colorRamp: unknown, numLegendColors?: number): string[]; - -export function getColor(colorRamp: unknown, i: number): string; diff --git a/src/plugins/maps_legacy/public/map/color_util.js b/src/plugins/maps_legacy/public/map/color_util.js deleted file mode 100644 index 64e4cedd7616c1..00000000000000 --- a/src/plugins/maps_legacy/public/map/color_util.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function getLegendColors(colorRamp, numLegendColors = 4) { - const colors = []; - colors[0] = getColor(colorRamp, 0); - for (let i = 1; i < numLegendColors - 1; i++) { - colors[i] = getColor(colorRamp, Math.floor((colorRamp.length * i) / numLegendColors)); - } - colors[numLegendColors - 1] = getColor(colorRamp, colorRamp.length - 1); - return colors; -} - -export function getColor(colorRamp, i) { - const color = colorRamp[i][1]; - const red = Math.floor(color[0] * 255); - const green = Math.floor(color[1] * 255); - const blue = Math.floor(color[2] * 255); - return `rgb(${red},${green},${blue})`; -} diff --git a/src/plugins/maps_legacy/public/map/geohash_columns.test.ts b/src/plugins/maps_legacy/public/map/geohash_columns.test.ts deleted file mode 100644 index e1da8ec506d9cc..00000000000000 --- a/src/plugins/maps_legacy/public/map/geohash_columns.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { geohashColumns } from './geohash_columns'; - -test('geohashColumns', () => { - expect(geohashColumns(1)).toBe(8); - expect(geohashColumns(2)).toBe(8 * 4); - expect(geohashColumns(3)).toBe(8 * 4 * 8); - expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4); -}); diff --git a/src/plugins/maps_legacy/public/map/geohash_columns.ts b/src/plugins/maps_legacy/public/map/geohash_columns.ts deleted file mode 100644 index 2140b4ea3d054c..00000000000000 --- a/src/plugins/maps_legacy/public/map/geohash_columns.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function geohashColumns(precision: number): number { - return geohashCells(precision, 0); -} - -/** - * Get the number of geohash cells for a given precision - * - * @param {number} precision the geohash precision (1<=precision<=12). - * @param {number} axis constant for the axis 0=lengthwise (ie. columns, along longitude), 1=heightwise (ie. rows, along latitude). - * @returns {number} Number of geohash cells (rows or columns) at that precision - */ -function geohashCells(precision: number, axis: number) { - let cells = 1; - for (let i = 1; i <= precision; i += 1) { - /* On odd precisions, rows divide by 4 and columns by 8. Vice-versa on even precisions */ - cells *= i % 2 === axis ? 4 : 8; - } - return cells; -} diff --git a/src/plugins/maps_legacy/public/map/index.scss b/src/plugins/maps_legacy/public/map/index.scss deleted file mode 100644 index f9fc841b9f868b..00000000000000 --- a/src/plugins/maps_legacy/public/map/index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './leaflet_overrides'; -@import './legend'; diff --git a/src/plugins/maps_legacy/public/map/kibana_map.js b/src/plugins/maps_legacy/public/map/kibana_map.js deleted file mode 100644 index 62dbbda2588a50..00000000000000 --- a/src/plugins/maps_legacy/public/map/kibana_map.js +++ /dev/null @@ -1,683 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EventEmitter } from 'events'; -import $ from 'jquery'; -import { get, isEqual, escape } from 'lodash'; -import { zoomToPrecision } from './zoom_to_precision'; -import { i18n } from '@kbn/i18n'; -import { ORIGIN } from '../../../maps_ems/common'; -import { L } from '../leaflet'; - -function makeFitControl(fitContainer, kibanaMap) { - // eslint-disable-next-line no-undef - const FitControl = L.Control.extend({ - options: { - position: 'topleft', - }, - initialize: function (fitContainer, kibanaMap) { - this._fitContainer = fitContainer; - this._kibanaMap = kibanaMap; - this._leafletMap = null; - }, - onAdd: function (leafletMap) { - this._leafletMap = leafletMap; - const fitDatBoundsLabel = i18n.translate( - 'maps_legacy.kibanaMap.leaflet.fitDataBoundsAriaLabel', - { defaultMessage: 'Fit Data Bounds' } - ); - $(this._fitContainer) - .html( - `` - ) - .on('click', (e) => { - e.preventDefault(); - this._kibanaMap.fitToData(); - }); - - return this._fitContainer; - }, - onRemove: function () { - $(this._fitContainer).off('click'); - }, - }); - - return new FitControl(fitContainer, kibanaMap); -} - -function makeLegendControl(container, kibanaMap, position) { - // eslint-disable-next-line no-undef - const LegendControl = L.Control.extend({ - options: { - position: 'topright', - }, - - initialize: function (container, kibanaMap, position) { - this._legendContainer = container; - this._kibanaMap = kibanaMap; - this.options.position = position; - }, - - updateContents() { - this._legendContainer.empty(); - const $div = $('

').addClass('visMapLegend'); - this._legendContainer.append($div); - const layers = this._kibanaMap.getLayers(); - layers.forEach((layer) => layer.appendLegendContents($div)); - }, - - onAdd: function () { - this._layerUpdateHandle = () => this.updateContents(); - this._kibanaMap.on('layers:update', this._layerUpdateHandle); - this.updateContents(); - return this._legendContainer.get(0); - }, - onRemove: function () { - this._kibanaMap.removeListener('layers:update', this._layerUpdateHandle); - this._legendContainer.empty(); - }, - }); - - return new LegendControl(container, kibanaMap, position); -} - -/** - * Collects map functionality required for Kibana. - * Serves as simple abstraction for leaflet as well. - */ -export class KibanaMap extends EventEmitter { - constructor(containerNode, options) { - super(); - this._containerNode = containerNode; - this._leafletBaseLayer = null; - this._baseLayerSettings = null; - this._baseLayerIsDesaturated = true; - - this._leafletDrawControl = null; - this._leafletFitControl = null; - this._leafletLegendControl = null; - this._legendPosition = 'topright'; - - this._layers = []; - this._listeners = []; - this._showTooltip = false; - - const leafletOptions = { - minZoom: options.minZoom, - maxZoom: options.maxZoom, - center: options.center ? options.center : [0, 0], - zoom: options.zoom ? options.zoom : 2, - // eslint-disable-next-line no-undef - renderer: L.canvas(), - zoomAnimation: false, // Desaturate map tiles causes animation rendering artifacts - zoomControl: options.zoomControl === undefined ? true : options.zoomControl, - }; - - // eslint-disable-next-line no-undef - this._leafletMap = L.map(containerNode, leafletOptions); - this._leafletMap.attributionControl.setPrefix(''); - - if (!options.scrollWheelZoom) { - this._leafletMap.scrollWheelZoom.disable(); - } - - let previousZoom = this._leafletMap.getZoom(); - this._leafletMap.on('zoomend', () => { - if (previousZoom !== this._leafletMap.getZoom()) { - previousZoom = this._leafletMap.getZoom(); - this.emit('zoomchange'); - } - }); - this._leafletMap.on('zoomend', () => this.emit('zoomend')); - this._leafletMap.on('dragend', () => this.emit('dragend')); - - this._leafletMap.on('zoomend', () => this._updateExtent()); - this._leafletMap.on('dragend', () => this._updateExtent()); - - this._leafletMap.on('mousemove', (e) => - this._layers.forEach((layer) => layer.movePointer('mousemove', e)) - ); - this._leafletMap.on('mouseout', (e) => - this._layers.forEach((layer) => layer.movePointer('mouseout', e)) - ); - this._leafletMap.on('mousedown', (e) => - this._layers.forEach((layer) => layer.movePointer('mousedown', e)) - ); - this._leafletMap.on('mouseup', (e) => - this._layers.forEach((layer) => layer.movePointer('mouseup', e)) - ); - this._leafletMap.on('draw:created', (event) => { - const drawType = event.layerType; - if (drawType === 'rectangle') { - const bounds = event.layer.getBounds(); - - const southEast = bounds.getSouthEast(); - const northWest = bounds.getNorthWest(); - let southEastLng = southEast.lng; - if (southEastLng > 180) { - southEastLng -= 360; - } - let northWestLng = northWest.lng; - if (northWestLng < -180) { - northWestLng += 360; - } - - const southEastLat = southEast.lat; - const northWestLat = northWest.lat; - - //Bounds cannot be created unless they form a box with larger than 0 dimensions - //Invalid areas are rejected by ES. - if (southEastLat === northWestLat || southEastLng === northWestLng) { - return; - } - - this.emit('drawCreated:rectangle', { - bounds: { - bottom_right: { - lat: southEastLat, - lon: southEastLng, - }, - top_left: { - lat: northWestLat, - lon: northWestLng, - }, - }, - }); - } else if (drawType === 'polygon') { - const latLongs = event.layer.getLatLngs()[0]; - this.emit('drawCreated:polygon', { - points: latLongs.map((leafletLatLng) => { - return { - lat: leafletLatLng.lat, - lon: leafletLatLng.lng, - }; - }), - }); - } - }); - - this.resize(); - } - - setShowTooltip(showTooltip) { - this._showTooltip = showTooltip; - } - - getLayers() { - return this._layers.slice(); - } - - addLayer(kibanaLayer) { - const onshowTooltip = (event) => { - if (!this._showTooltip) { - return; - } - - if (!this._popup) { - // eslint-disable-next-line no-undef - this._popup = new L.ResponsivePopup({ autoPan: false }); - this._popup.setLatLng(event.position); - this._popup.setContent(event.content); - this._leafletMap.openPopup(this._popup); - } else { - if (!this._popup.getLatLng().equals(event.position)) { - this._popup.setLatLng(event.position); - } - if (this._popup.getContent() !== event.content) { - this._popup.setContent(event.content); - } - } - }; - - kibanaLayer.on('showTooltip', onshowTooltip); - this._listeners.push({ name: 'showTooltip', handle: onshowTooltip, layer: kibanaLayer }); - - const onHideTooltip = () => { - this._leafletMap.closePopup(); - this._popup = null; - }; - kibanaLayer.on('hideTooltip', onHideTooltip); - this._listeners.push({ name: 'hideTooltip', handle: onHideTooltip, layer: kibanaLayer }); - - const onStyleChanged = () => { - if (this._leafletLegendControl) { - this._leafletLegendControl.updateContents(); - } - }; - kibanaLayer.on('styleChanged', onStyleChanged); - this._listeners.push({ name: 'styleChanged', handle: onStyleChanged, layer: kibanaLayer }); - - this._layers.push(kibanaLayer); - kibanaLayer.addToLeafletMap(this._leafletMap); - this.emit('layers:update'); - - this._addAttributions(kibanaLayer.getAttributions()); - } - - removeLayer(kibanaLayer) { - if (!kibanaLayer) { - return; - } - - this._removeAttributions(kibanaLayer.getAttributions()); - const index = this._layers.indexOf(kibanaLayer); - if (index >= 0) { - this._layers.splice(index, 1); - kibanaLayer.removeFromLeafletMap(this._leafletMap); - } - this._listeners.forEach((listener) => { - if (listener.layer === kibanaLayer) { - listener.layer.removeListener(listener.name, listener.handle); - } - }); - - //must readd all attributions, because we might have removed dupes - this._layers.forEach((layer) => this._addAttributions(layer.getAttributions())); - if (this._baseLayerSettings) { - this._addAttributions(this._baseLayerSettings.options.attribution); - } - } - - _addAttributions(attribution) { - const attributions = getAttributionArray(attribution); - attributions.forEach((attribution) => { - this._leafletMap.attributionControl.removeAttribution(attribution); //this ensures we do not add duplicates - this._leafletMap.attributionControl.addAttribution(attribution); - }); - } - - _removeAttributions(attribution) { - const attributions = getAttributionArray(attribution); - attributions.forEach((attribution) => { - this._leafletMap.attributionControl.removeAttribution(attribution); //this ensures we do not add duplicates - }); - } - - destroy() { - if (this._leafletFitControl) { - this._leafletMap.removeControl(this._leafletFitControl); - } - if (this._leafletDrawControl) { - this._leafletMap.removeControl(this._leafletDrawControl); - } - if (this._leafletLegendControl) { - this._leafletMap.removeControl(this._leafletLegendControl); - } - this.setBaseLayer(null); - let layer; - while (this._layers.length) { - layer = this._layers.pop(); - layer.removeFromLeafletMap(this._leafletMap); - } - this._leafletMap.remove(); - this._containerNode.innerHTML = ''; - this._listeners.forEach((listener) => - listener.layer.removeListener(listener.name, listener.handle) - ); - } - - getCenter() { - const center = this._leafletMap.getCenter(); - return { lon: center.lng, lat: center.lat }; - } - - setCenter(latitude, longitude) { - // eslint-disable-next-line no-undef - const latLong = L.latLng(latitude, longitude); - if (latLong.equals && !latLong.equals(this._leafletMap.getCenter())) { - this._leafletMap.setView(latLong); - } - } - - setZoomLevel(zoomLevel) { - if (this._leafletMap.getZoom() !== zoomLevel) { - this._leafletMap.setZoom(zoomLevel); - } - } - - getZoomLevel = () => { - return this._leafletMap.getZoom(); - }; - - getMaxZoomLevel = () => { - return this._leafletMap.getMaxZoom(); - }; - - getGeohashPrecision() { - return zoomToPrecision(this._leafletMap.getZoom(), 12, this._leafletMap.getMaxZoom()); - } - - getLeafletBounds() { - return this._leafletMap.getBounds(); - } - - getMetersPerPixel() { - const pointC = this._leafletMap.latLngToContainerPoint(this._leafletMap.getCenter()); // center (pixels) - const pointX = [pointC.x + 1, pointC.y]; // add one pixel to x - const pointY = [pointC.x, pointC.y + 1]; // add one pixel to y - - const latLngC = this._leafletMap.containerPointToLatLng(pointC); - const latLngX = this._leafletMap.containerPointToLatLng(pointX); - const latLngY = this._leafletMap.containerPointToLatLng(pointY); - - const distanceX = latLngC.distanceTo(latLngX); // calculate distance between c and x (latitude) - const distanceY = latLngC.distanceTo(latLngY); // calculate distance between c and y (longitude) - return Math.min(distanceX, distanceY); - } - - _getLeafletBounds(resizeOnFail) { - const boundsRaw = this._leafletMap.getBounds(); - const bounds = this._leafletMap.wrapLatLngBounds(boundsRaw); - - if (!bounds) { - return null; - } - - const southEast = bounds.getSouthEast(); - const northWest = bounds.getNorthWest(); - if (southEast.lng === northWest.lng || southEast.lat === northWest.lat) { - if (resizeOnFail) { - this._leafletMap.invalidateSize(); - return this._getLeafletBounds(false); - } else { - return null; - } - } else { - return bounds; - } - } - - getBounds() { - const bounds = this._getLeafletBounds(true); - if (!bounds) { - return null; - } - - const southEast = bounds.getSouthEast(); - const northWest = bounds.getNorthWest(); - - const southEastLng = southEast.lng; - const northWestLng = northWest.lng; - const southEastLat = southEast.lat; - const northWestLat = northWest.lat; - - // When map has not width or height, the map has no dimensions. - // These dimensions are enforced due to CSS style rules that enforce min-width/height of 0 - // that enforcement also resolves errors with the heatmap layer plugin. - - return { - bottom_right: { - lat: southEastLat, - lon: southEastLng, - }, - top_left: { - lat: northWestLat, - lon: northWestLng, - }, - }; - } - - setDesaturateBaseLayer(isDesaturated) { - if (isDesaturated === this._baseLayerIsDesaturated) { - return; - } - this._baseLayerIsDesaturated = isDesaturated; - this._updateDesaturation(); - if (this._leafletBaseLayer) { - this._leafletBaseLayer.redraw(); - } - } - - addDrawControl() { - const drawColor = '#000'; - const drawOptions = { - draw: { - polyline: false, - marker: false, - circle: false, - rectangle: { - shapeOptions: { - stroke: false, - color: drawColor, - }, - }, - polygon: { - shapeOptions: { - color: drawColor, - }, - }, - circlemarker: false, - }, - }; - // eslint-disable-next-line no-undef - this._leafletDrawControl = new L.Control.Draw(drawOptions); - this._leafletMap.addControl(this._leafletDrawControl); - } - - addFitControl() { - if (this._leafletFitControl || !this._leafletMap) { - return; - } - - // eslint-disable-next-line no-undef - const fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit'); - this._leafletFitControl = makeFitControl(fitContainer, this); - this._leafletMap.addControl(this._leafletFitControl); - } - - addLegendControl() { - if (this._leafletLegendControl || !this._leafletMap) { - return; - } - this._updateLegend(); - } - - setLegendPosition(position) { - if (this._legendPosition === position) { - if (!this._leafletLegendControl) { - this._updateLegend(); - } - } else { - this._legendPosition = position; - this._updateLegend(); - } - } - - _updateLegend() { - if (this._leafletLegendControl) { - this._leafletMap.removeControl(this._leafletLegendControl); - } - const $wrapper = $('
').addClass('visMapLegend__wrapper'); - this._leafletLegendControl = makeLegendControl($wrapper, this, this._legendPosition); - this._leafletMap.addControl(this._leafletLegendControl); - } - - resize() { - this._leafletMap.invalidateSize(); - this._updateExtent(); - } - - setMinZoom(zoom) { - this._leafletMap.setMinZoom(zoom); - } - - setMaxZoom(zoom) { - this._leafletMap.setMaxZoom(zoom); - } - - getLeafletBaseLayer() { - return this._leafletBaseLayer; - } - - setBaseLayer(settings) { - if (isEqual(settings, this._baseLayerSettings)) { - return; - } - - if (settings === null) { - if (this._leafletBaseLayer && this._leafletMap) { - this._removeAttributions(this._baseLayerSettings.options.attribution); - this._leafletMap.removeLayer(this._leafletBaseLayer); - this._leafletBaseLayer = null; - this._baseLayerSettings = null; - } - return; - } - - this._baseLayerSettings = settings; - if (this._leafletBaseLayer) { - this._leafletMap.removeLayer(this._leafletBaseLayer); - this._leafletBaseLayer = null; - } - - let baseLayer; - if (settings.baseLayerType === 'wms') { - //This is user-input that is rendered with the Leaflet attribution control. Needs to be sanitized. - this._baseLayerSettings.options.attribution = escape(settings.options.attribution); - baseLayer = this._getWMSBaseLayer(settings.options); - } else if (settings.baseLayerType === 'tms') { - baseLayer = this._getTMSBaseLayer(settings.options); - } - - if (baseLayer) { - baseLayer.on('tileload', () => this._updateDesaturation()); - baseLayer.on('load', () => { - this.emit('baseLayer:loaded'); - }); - baseLayer.on('loading', () => { - this.emit('baseLayer:loading'); - }); - - this._leafletBaseLayer = baseLayer; - this._leafletBaseLayer.addTo(this._leafletMap); - this._leafletBaseLayer.bringToBack(); - if (settings.options.minZoom > this._leafletMap.getZoom()) { - this._leafletMap.setZoom(settings.options.minZoom); - } - this._addAttributions(settings.options.attribution); - this.resize(); - } - } - - isInside(bucketRectBounds) { - const mapBounds = this._leafletMap.getBounds(); - return mapBounds.intersects(bucketRectBounds); - } - - async fitToData() { - if (!this._leafletMap) { - return; - } - - const boundsArray = await Promise.all( - this._layers.map(async (layer) => { - return await layer.getBounds(); - }) - ); - - let bounds = null; - boundsArray.forEach(async (b) => { - if (bounds) { - bounds.extend(b); - } else { - bounds = b; - } - }); - - if (bounds && bounds.isValid()) { - this._leafletMap.fitBounds(bounds); - } - } - - _getTMSBaseLayer(options) { - // eslint-disable-next-line no-undef - return L.tileLayer(options.url, { - minZoom: options.minZoom, - maxZoom: options.maxZoom, - subdomains: options.subdomains || [], - }); - } - - _getWMSBaseLayer(options) { - const wmsOptions = { - format: options.format || '', - layers: options.layers || '', - minZoom: options.minZoom, - maxZoom: options.maxZoom, - styles: options.styles || '', - transparent: options.transparent, - version: options.version || '1.3.0', - }; - - return typeof options.url === 'string' && options.url.length - ? // eslint-disable-next-line no-undef - L.tileLayer.wms(options.url, wmsOptions) - : null; - } - - _updateExtent() { - this._layers.forEach((layer) => layer.updateExtent()); - } - - _updateDesaturation() { - const tiles = $('img.leaflet-tile-loaded'); - // Don't apply client-side styling to EMS basemaps - if (get(this._baseLayerSettings, 'options.origin') === ORIGIN.EMS) { - tiles.addClass('filters-off'); - } else { - if (this._baseLayerIsDesaturated) { - tiles.removeClass('filters-off'); - } else if (!this._baseLayerIsDesaturated) { - tiles.addClass('filters-off'); - } - } - } - - persistUiStateForVisualization(uiState) { - function persistMapStateInUiState() { - const centerFromUIState = uiState.get('mapCenter'); - const zoomFromUiState = parseInt(uiState.get('mapZoom')); - - if (isNaN(zoomFromUiState) || this.getZoomLevel() !== zoomFromUiState) { - uiState.set('mapZoom', this.getZoomLevel()); - } - const centerFromMap = this.getCenter(); - if ( - !centerFromUIState || - centerFromMap.lon !== centerFromUIState[1] || - centerFromMap.lat !== centerFromUIState[0] - ) { - uiState.set('mapCenter', [centerFromMap.lat, centerFromMap.lon]); - } - } - - this.on('dragend', persistMapStateInUiState); - this.on('zoomend', persistMapStateInUiState); - } - - useUiStateFromVisualization(uiState) { - const zoomFromUiState = parseInt(uiState?.get('mapZoom')); - const centerFromUIState = uiState?.get('mapCenter'); - if (!isNaN(zoomFromUiState)) { - this.setZoomLevel(zoomFromUiState); - } - if (centerFromUIState) { - this.setCenter(centerFromUIState[0], centerFromUIState[1]); - } - } -} - -function getAttributionArray(attribution) { - const attributionString = attribution || ''; - let attributions = attributionString.split(/\s*\|\s*/); - if (attributions.length === 1) { - //temp work-around due to inconsistency in manifests of how attributions are delimited - attributions = attributions[0].split(','); - } - return attributions; -} diff --git a/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts b/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts deleted file mode 100644 index 9d3f982bd78b2c..00000000000000 --- a/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export class KibanaMapLayer { - constructor(); - - getBounds(): Promise; - - addToLeafletMap(leafletMap: unknown): void; - - removeFromLeafletMap(leafletMap: unknown): void; - - appendLegendContents(): void; - - updateExtent(): void; - - movePointer(): void; - - getAttributions(): unknown; -} diff --git a/src/plugins/maps_legacy/public/map/kibana_map_layer.js b/src/plugins/maps_legacy/public/map/kibana_map_layer.js deleted file mode 100644 index 877ddec5f332f6..00000000000000 --- a/src/plugins/maps_legacy/public/map/kibana_map_layer.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EventEmitter } from 'events'; - -export class KibanaMapLayer extends EventEmitter { - constructor() { - super(); - this._leafletLayer = null; - } - - async getBounds() { - return this._leafletLayer.getBounds(); - } - - addToLeafletMap(leafletMap) { - this._leafletLayer.addTo(leafletMap); - } - - removeFromLeafletMap(leafletMap) { - leafletMap.removeLayer(this._leafletLayer); - } - - appendLegendContents() {} - - updateExtent() {} - - movePointer() {} - - getAttributions() { - return this._attribution; - } -} diff --git a/src/plugins/maps_legacy/public/map/precision.ts b/src/plugins/maps_legacy/public/map/precision.ts deleted file mode 100644 index bf81f211e41af9..00000000000000 --- a/src/plugins/maps_legacy/public/map/precision.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// @ts-ignore -import { getUiSettings } from '../kibana_services'; -import { geohashColumns } from './geohash_columns'; - -/** - * Get the number of geohash columns (world-wide) for a given precision - * @param precision the geohash precision - * @returns {number} the number of columns - */ - -const DEFAULT_PRECISION = 2; - -function getMaxPrecision() { - const config = getUiSettings(); - return parseInt(config.get('visualization:tileMap:maxPrecision'), 10) || 12; -} - -export function getZoomPrecision() { - /** - * Map Leaflet zoom levels to geohash precision levels. - * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. - */ - const zoomPrecision: any = {}; - const minGeohashPixels = 16; - const maxPrecision = getMaxPrecision(); - - for (let zoom = 0; zoom <= 21; zoom += 1) { - const worldPixels = 256 * Math.pow(2, zoom); - zoomPrecision[zoom] = 1; - for (let precision = 2; precision <= maxPrecision; precision += 1) { - const columns = geohashColumns(precision); - if (worldPixels / columns >= minGeohashPixels) { - zoomPrecision[zoom] = precision; - } else { - break; - } - } - } - return zoomPrecision; -} - -export function getPrecision(val: string) { - let precision = parseInt(val, 10); - const maxPrecision = getMaxPrecision(); - - if (Number.isNaN(precision)) { - precision = DEFAULT_PRECISION; - } - - if (precision > maxPrecision) { - return maxPrecision; - } - - return precision; -} diff --git a/src/plugins/maps_legacy/public/map/zoom_to_precision.ts b/src/plugins/maps_legacy/public/map/zoom_to_precision.ts deleted file mode 100644 index 6ff43c291e400c..00000000000000 --- a/src/plugins/maps_legacy/public/map/zoom_to_precision.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { geohashColumns } from './geohash_columns'; - -const defaultMaxPrecision = 12; -const minGeoHashPixels = 16; - -const calculateZoomToPrecisionMap = (maxZoom: number): Map => { - /** - * Map Leaflet zoom levels to geohash precision levels. - * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. - */ - const zoomPrecisionMap = new Map(); - - for (let zoom = 0; zoom <= maxZoom; zoom += 1) { - if (typeof zoomPrecisionMap.get(zoom) === 'number') { - continue; - } - - const worldPixels = 256 * Math.pow(2, zoom); - - zoomPrecisionMap.set(zoom, 1); - - for (let precision = 2; precision <= defaultMaxPrecision; precision += 1) { - const columns = geohashColumns(precision); - - if (worldPixels / columns >= minGeoHashPixels) { - zoomPrecisionMap.set(zoom, precision); - } else { - break; - } - } - } - - return zoomPrecisionMap; -}; - -export function zoomToPrecision(mapZoom: number, maxPrecision: number, maxZoom: number) { - const zoomPrecisionMap = calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21); - const precision = zoomPrecisionMap.get(mapZoom); - - return precision ? Math.min(precision, maxPrecision) : maxPrecision; -} diff --git a/src/plugins/maps_legacy/public/plugin.ts b/src/plugins/maps_legacy/public/plugin.ts deleted file mode 100644 index 63b3c3845f5490..00000000000000 --- a/src/plugins/maps_legacy/public/plugin.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// @ts-ignore -import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; -// @ts-ignore -import { - setToasts, - setUiSettings, - setMapsEmsConfig, - setGetServiceSettings, -} from './kibana_services'; -// @ts-ignore -import { getPrecision, getZoomPrecision } from './map/precision'; -import { MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; -import { MapsLegacyConfig } from '../config'; -// @ts-ignore -import { BaseMapsVisualizationProvider } from './map/base_maps_visualization'; -import type { MapsEmsPluginSetup } from '../../maps_ems/public'; - -/** - * These are the interfaces with your public contracts. You should export these - * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. - * @public - */ - -export interface MapsLegacySetupDependencies { - mapsEms: MapsEmsPluginSetup; -} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface MapsLegacyStartDependencies {} - -export class MapsLegacyPlugin implements Plugin { - readonly _initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this._initializerContext = initializerContext; - } - - public setup(core: CoreSetup, plugins: MapsLegacySetupDependencies) { - setToasts(core.notifications.toasts); - setUiSettings(core.uiSettings); - setMapsEmsConfig(plugins.mapsEms.config); - setGetServiceSettings(plugins.mapsEms.getServiceSettings); - - const getBaseMapsVis = () => new BaseMapsVisualizationProvider(); - - return { - getZoomPrecision, - getPrecision, - getBaseMapsVis, - }; - } - - public start(core: CoreStart, plugins: MapsLegacyStartDependencies) {} -} diff --git a/src/plugins/maps_legacy/public/tooltip_provider.d.ts b/src/plugins/maps_legacy/public/tooltip_provider.d.ts deleted file mode 100644 index 33a17cc472d6c0..00000000000000 --- a/src/plugins/maps_legacy/public/tooltip_provider.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function mapTooltipProvider(element: unknown, formatter: unknown): () => unknown; diff --git a/src/plugins/maps_legacy/public/tooltip_provider.js b/src/plugins/maps_legacy/public/tooltip_provider.js deleted file mode 100644 index d774f19be39a89..00000000000000 --- a/src/plugins/maps_legacy/public/tooltip_provider.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import ReactDOMServer from 'react-dom/server'; - -function getToolTipContent(details) { - return ReactDOMServer.renderToStaticMarkup( - - - {details.map((detail, i) => ( - - - - - ))} - -
{detail.label}{detail.value}
- ); -} - -export function mapTooltipProvider(element, formatter) { - return (...args) => { - const details = formatter(...args); - return details && getToolTipContent(details); - }; -} diff --git a/src/plugins/maps_legacy/server/index.ts b/src/plugins/maps_legacy/server/index.ts deleted file mode 100644 index 57a7bfdd1828f4..00000000000000 --- a/src/plugins/maps_legacy/server/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Plugin, PluginConfigDescriptor } from 'kibana/server'; -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; -import { configSchema, MapsLegacyConfig } from '../config'; -import { getUiSettings } from './ui_settings'; - -export const config: PluginConfigDescriptor = { - exposeToBrowser: {}, - schema: configSchema, -}; - -export interface MapsLegacyPluginSetup { - config: MapsLegacyConfig; -} - -export class MapsLegacyPlugin implements Plugin { - readonly _initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this._initializerContext = initializerContext; - } - - public setup(core: CoreSetup) { - core.uiSettings.register(getUiSettings()); - - const pluginConfig = this._initializerContext.config.get(); - return { - config: pluginConfig, - }; - } - - public start() {} -} - -export const plugin = (initializerContext: PluginInitializerContext) => - new MapsLegacyPlugin(initializerContext); diff --git a/src/plugins/maps_legacy/server/ui_settings.ts b/src/plugins/maps_legacy/server/ui_settings.ts deleted file mode 100644 index fe516de8221493..00000000000000 --- a/src/plugins/maps_legacy/server/ui_settings.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { UiSettingsParams } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; - -export function getUiSettings(): Record> { - return { - 'visualization:tileMap:maxPrecision': { - name: i18n.translate('maps_legacy.advancedSettings.visualization.tileMap.maxPrecisionTitle', { - defaultMessage: 'Maximum tile map precision', - }), - value: 7, - description: i18n.translate( - 'maps_legacy.advancedSettings.visualization.tileMap.maxPrecisionText', - { - defaultMessage: - 'The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, 12 is the max. {cellDimensionsLink}', - description: - 'Part of composite text: maps_legacy.advancedSettings.visualization.tileMap.maxPrecisionText + ' + - 'maps_legacy.advancedSettings.visualization.tileMap.maxPrecision.cellDimensionsLinkText', - values: { - cellDimensionsLink: - `` + - i18n.translate( - 'maps_legacy.advancedSettings.visualization.tileMap.maxPrecision.cellDimensionsLinkText', - { - defaultMessage: 'Explanation of cell dimensions', - } - ) + - '', - }, - } - ), - schema: schema.number(), - category: ['visualization'], - }, - 'visualization:tileMap:WMSdefaults': { - name: i18n.translate('maps_legacy.advancedSettings.visualization.tileMap.wmsDefaultsTitle', { - defaultMessage: 'Default WMS properties', - }), - value: JSON.stringify( - { - enabled: false, - url: '', - options: { - version: '', - layers: '', - format: 'image/png', - transparent: true, - attribution: '', - styles: '', - }, - }, - null, - 2 - ), - type: 'json', - description: i18n.translate( - 'maps_legacy.advancedSettings.visualization.tileMap.wmsDefaultsText', - { - defaultMessage: - 'Default {propertiesLink} for the WMS map server support in the coordinate map', - description: - 'Part of composite text: maps_legacy.advancedSettings.visualization.tileMap.wmsDefaultsText + ' + - 'maps_legacy.advancedSettings.visualization.tileMap.wmsDefaults.propertiesLinkText', - values: { - propertiesLink: - '' + - i18n.translate( - 'maps_legacy.advancedSettings.visualization.tileMap.wmsDefaults.propertiesLinkText', - { - defaultMessage: 'properties', - } - ) + - '', - }, - } - ), - schema: schema.object({ - enabled: schema.boolean(), - url: schema.string(), - options: schema.object({ - version: schema.string(), - layers: schema.string(), - format: schema.string(), - transparent: schema.boolean(), - attribution: schema.string(), - styles: schema.string(), - }), - }), - category: ['visualization'], - }, - }; -} diff --git a/src/plugins/maps_legacy/tsconfig.json b/src/plugins/maps_legacy/tsconfig.json deleted file mode 100644 index b6fcb9345b1ce8..00000000000000 --- a/src/plugins/maps_legacy/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./target/types", - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": ["public/**/*", "server/**/*", "config.ts"], - "references": [ - { "path": "../vis_default_editor/tsconfig.json" }, - { "path": "../maps_ems/tsconfig.json" } - ] -} diff --git a/src/plugins/region_map/README.md b/src/plugins/region_map/README.md deleted file mode 100644 index 540ab47c102d3e..00000000000000 --- a/src/plugins/region_map/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Region map visualization - -Create choropleth maps. Display the results of a term-aggregation as e.g. countries, zip-codes, states. - -This plugin is targeted for removal in 8.0. \ No newline at end of file diff --git a/src/plugins/region_map/jest.config.js b/src/plugins/region_map/jest.config.js deleted file mode 100644 index 1107c994c0443d..00000000000000 --- a/src/plugins/region_map/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/src/plugins/region_map'], -}; diff --git a/src/plugins/region_map/kibana.json b/src/plugins/region_map/kibana.json deleted file mode 100644 index 1a24b6f8f05e8b..00000000000000 --- a/src/plugins/region_map/kibana.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id": "regionMap", - "owner": { - "name": "GIS", - "githubTeam": "kibana-gis" - }, - "version": "8.0.0", - "kibanaVersion": "kibana", - "ui": true, - "server": true, - "requiredPlugins": [ - "visualizations", - "expressions", - "mapsLegacy", - "mapsEms", - "kibanaLegacy", - "data", - "share" - ], - "requiredBundles": ["kibanaUtils", "charts", "visDefaultEditor"] -} diff --git a/src/plugins/region_map/package.json b/src/plugins/region_map/package.json deleted file mode 100644 index 609ab2706f9c05..00000000000000 --- a/src/plugins/region_map/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "region_map", - "version": "kibana" -} \ No newline at end of file diff --git a/src/plugins/region_map/public/__snapshots__/region_map_fn.test.ts.snap b/src/plugins/region_map/public/__snapshots__/region_map_fn.test.ts.snap deleted file mode 100644 index df72e75f5ad6bb..00000000000000 --- a/src/plugins/region_map/public/__snapshots__/region_map_fn.test.ts.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`interpreter/functions#regionmap returns an object with the correct structure 1`] = ` -Object { - "as": "region_map_vis", - "type": "render", - "value": Object { - "visConfig": Object { - "addTooltip": true, - "colorSchema": "Yellow to Red", - "emsHotLink": "", - "isDisplayWarning": true, - "legendPosition": "bottomright", - "mapCenter": Array [ - 0, - 0, - ], - "mapZoom": 2, - "metric": Object { - "accessor": 0, - "aggType": "count", - "format": Object { - "id": "number", - }, - "params": Object {}, - }, - "outlineWeight": 1, - "selectedJoinField": null, - "showAllShapes": true, - "wms": Object { - "enabled": false, - "options": Object { - "format": "image/png", - "transparent": true, - }, - }, - }, - "visData": Object { - "columns": Array [ - Object { - "id": "col-0-1", - "name": "Count", - }, - ], - "rows": Array [ - Object { - "col-0-1": 0, - }, - ], - "type": "datatable", - }, - "visType": "region_map", - }, -} -`; diff --git a/src/plugins/region_map/public/choropleth_layer.js b/src/plugins/region_map/public/choropleth_layer.js deleted file mode 100644 index fe4dcb0432a135..00000000000000 --- a/src/plugins/region_map/public/choropleth_layer.js +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import $ from 'jquery'; -import _ from 'lodash'; -import d3 from 'd3'; -import { i18n } from '@kbn/i18n'; -import * as topojson from 'topojson-client'; -import { getNotifications } from './kibana_services'; -import { colorUtil, KibanaMapLayer } from '../../maps_legacy/public'; -import { truncatedColorMaps } from '../../charts/public'; - -const EMPTY_STYLE = { - weight: 1, - opacity: 0.6, - color: 'rgb(200,200,200)', - fillOpacity: 0, -}; - -export class ChoroplethLayer extends KibanaMapLayer { - static _doInnerJoin(sortedMetrics, sortedGeojsonFeatures, joinField) { - let j = 0; - for (let i = 0; i < sortedGeojsonFeatures.length; i++) { - const property = sortedGeojsonFeatures[i].properties[joinField]; - sortedGeojsonFeatures[i].__kbnJoinedMetric = null; - const position = sortedMetrics.length - ? compareLexicographically(property, sortedMetrics[j].term) - : -1; - if (position === -1) { - //just need to cycle on - } else if (position === 0) { - sortedGeojsonFeatures[i].__kbnJoinedMetric = sortedMetrics[j]; - } else if (position === 1) { - //needs to catch up - while (j < sortedMetrics.length) { - const newTerm = sortedMetrics[j].term; - const newPosition = compareLexicographically(newTerm, property); - if (newPosition === -1) { - //not far enough - } else if (newPosition === 0) { - sortedGeojsonFeatures[i].__kbnJoinedMetric = sortedMetrics[j]; - break; - } else if (newPosition === 1) { - //too far! - break; - } - if (j === sortedMetrics.length - 1) { - //always keep a reference to the last metric - break; - } else { - j++; - } - } - } - } - } - - constructor( - name, - attribution, - format, - showAllShapes, - meta, - layerConfig, - serviceSettings, - leaflet - ) { - super(); - this._serviceSettings = serviceSettings; - this._metrics = null; - this._joinField = null; - this._colorRamp = truncatedColorMaps[Object.keys(truncatedColorMaps)[0]].value; - this._lineWeight = 1; - this._tooltipFormatter = () => ''; - this._attribution = attribution; - this._boundsOfData = null; - this._showAllShapes = showAllShapes; - this._layerName = name; - this._layerConfig = layerConfig; - this._leaflet = leaflet; - - // eslint-disable-next-line no-undef - this._leafletLayer = this._leaflet.geoJson(null, { - onEachFeature: (feature, layer) => { - layer.on('click', () => { - this.emit('select', feature.properties[this._joinField]); - }); - let location = null; - layer.on({ - mouseover: () => { - const tooltipContents = this._tooltipFormatter(feature); - if (!location) { - // eslint-disable-next-line no-undef - const leafletGeojson = this._leaflet.geoJson(feature); - location = leafletGeojson.getBounds().getCenter(); - } - this.emit('showTooltip', { - content: tooltipContents, - position: location, - }); - }, - mouseout: () => { - this.emit('hideTooltip'); - }, - }); - }, - style: this._makeEmptyStyleFunction(), - }); - - this._loaded = false; - this._error = false; - this._isJoinValid = false; - this._whenDataLoaded = new Promise(async (resolve) => { - try { - const data = await this._makeJsonAjaxCall(); - let featureCollection; - let formatType; - if (typeof format === 'string') { - formatType = format; - } else if (format && format.type) { - formatType = format.type; - } else { - formatType = 'geojson'; - } - - if (formatType === 'geojson') { - featureCollection = data; - } else if (formatType === 'topojson') { - const features = _.get(data, 'objects.' + meta.feature_collection_path); - featureCollection = topojson.feature(data, features); //conversion to geojson - } else { - //should never happen - throw new Error( - i18n.translate('regionMap.choroplethLayer.unrecognizedFormatErrorMessage', { - defaultMessage: 'Unrecognized format {formatType}', - values: { formatType }, - }) - ); - } - this._sortedFeatures = featureCollection.features.slice(); - this._sortFeatures(); - - if (showAllShapes) { - this._leafletLayer.addData(featureCollection); - } else { - //we need to delay adding the data until we have performed the join and know which features - //should be displayed - } - this._loaded = true; - this._setStyle(); - resolve(); - } catch (e) { - this._loaded = true; - this._error = true; - - let errorMessage; - if (e.status === 404) { - errorMessage = i18n.translate( - 'regionMap.choroplethLayer.downloadingVectorData404ErrorMessage', - { - defaultMessage: - "Server responding with '404' when attempting to fetch {name}. \ -Make sure the file exists at that location.", - values: { name: name }, - } - ); - } else { - errorMessage = i18n.translate( - 'regionMap.choroplethLayer.downloadingVectorDataErrorMessage', - { - defaultMessage: - 'Cannot download {name} file. Please ensure the \ -CORS configuration of the server permits requests from the Kibana application on this host.', - values: { name: name }, - } - ); - } - - getNotifications().toasts.addDanger({ - title: i18n.translate( - 'regionMap.choroplethLayer.downloadingVectorDataErrorMessageTitle', - { - defaultMessage: 'Error downloading vector data', - } - ), - text: errorMessage, - }); - - resolve(); - } - }); - } - - //This method is stubbed in the tests to avoid network request during unit tests. - async _makeJsonAjaxCall() { - return this._serviceSettings.getJsonForRegionLayer(this._layerConfig); - } - - _invalidateJoin() { - this._isJoinValid = false; - } - - _doInnerJoin() { - ChoroplethLayer._doInnerJoin(this._metrics, this._sortedFeatures, this._joinField); - this._isJoinValid = true; - } - - _setStyle() { - if (this._error || !this._loaded || !this._metrics || !this._joinField) { - return; - } - - if (!this._isJoinValid) { - this._doInnerJoin(); - if (!this._showAllShapes) { - const featureCollection = { - type: 'FeatureCollection', - features: this._sortedFeatures.filter((feature) => feature.__kbnJoinedMetric), - }; - this._leafletLayer.addData(featureCollection); - } - } - - const styler = this._makeChoroplethStyler(); - this._leafletLayer.setStyle(styler.leafletStyleFunction); - - if (this._metrics && this._metrics.length > 0) { - const { min, max } = getMinMax(this._metrics); - this._legendColors = colorUtil.getLegendColors(this._colorRamp); - const quantizeDomain = min !== max ? [min, max] : d3.scale.quantize().domain(); - this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors); - } - this._boundsOfData = styler.getLeafletBounds(); - this.emit('styleChanged', { - mismatches: styler.getMismatches(), - }); - } - - getUrl() { - return this._layerName; - } - - setTooltipFormatter(tooltipFormatter, fieldFormatter, fieldName, metricLabel) { - this._tooltipFormatter = (geojsonFeature) => { - if (!this._metrics) { - return ''; - } - const match = this._metrics.find((bucket) => { - return ( - compareLexicographically(bucket.term, geojsonFeature.properties[this._joinField]) === 0 - ); - }); - return tooltipFormatter(match, fieldFormatter, fieldName, metricLabel); - }; - } - - setJoinField(joinfield) { - if (joinfield === this._joinField) { - return; - } - this._joinField = joinfield; - this._sortFeatures(); - this._setStyle(); - } - - cloneChoroplethLayerForNewData( - name, - attribution, - format, - showAllData, - meta, - layerConfig, - serviceSettings, - leaflet - ) { - const clonedLayer = new ChoroplethLayer( - name, - attribution, - format, - showAllData, - meta, - layerConfig, - serviceSettings, - leaflet - ); - clonedLayer.setJoinField(this._joinField); - clonedLayer.setColorRamp(this._colorRamp); - clonedLayer.setLineWeight(this._lineWeight); - clonedLayer.setTooltipFormatter(this._tooltipFormatter); - if (this._metrics) { - clonedLayer.setMetrics(this._metrics, this._valueFormatter, this._metricTitle); - } - return clonedLayer; - } - - _sortFeatures() { - if (this._sortedFeatures && this._joinField) { - this._sortedFeatures.sort((a, b) => { - const termA = a.properties[this._joinField]; - const termB = b.properties[this._joinField]; - return compareLexicographically(termA, termB); - }); - this._invalidateJoin(); - } - } - - whenDataLoaded() { - return this._whenDataLoaded; - } - - setMetrics(metrics, fieldFormatter, metricTitle) { - this._metrics = metrics.slice(); - this._valueFormatter = fieldFormatter; - this._metricTitle = metricTitle; - - this._metrics.sort((a, b) => compareLexicographically(a.term, b.term)); - this._invalidateJoin(); - this._setStyle(); - } - - setColorRamp(colorRamp) { - if (_.isEqual(colorRamp, this._colorRamp)) { - return; - } - this._colorRamp = colorRamp; - this._setStyle(); - } - - setLineWeight(lineWeight) { - if (this._lineWeight === lineWeight) { - return; - } - this._lineWeight = lineWeight; - this._setStyle(); - } - - canReuseInstance(name, showAllShapes) { - return this._layerName === name && this._showAllShapes === showAllShapes; - } - - canReuseInstanceForNewMetrics(name, showAllShapes, newMetrics) { - if (this._layerName !== name) { - return false; - } - - if (showAllShapes) { - return this._showAllShapes === showAllShapes; - } - - if (!this._metrics) { - return; - } - - const currentKeys = Object.keys(this._metrics); - const newKeys = Object.keys(newMetrics); - return _.isEqual(currentKeys, newKeys); - } - - getBounds() { - const bounds = super.getBounds(); - return this._boundsOfData ? this._boundsOfData : bounds; - } - - appendLegendContents(jqueryDiv) { - if (!this._legendColors || !this._legendQuantizer) { - return; - } - - const titleText = this._metricTitle; - const $title = $('
').addClass('visMapLegend__title').text(titleText); - jqueryDiv.append($title); - - this._legendColors.forEach((color) => { - const labelText = this._legendQuantizer - .invertExtent(color) - .map((val) => { - return this._valueFormatter.convert(val); - }) - .join(' – '); - - const label = $('
'); - const icon = $('').css({ - background: color, - 'border-color': makeColorDarker(color), - }); - - const text = $('').text(labelText); - label.append(icon); - label.append(text); - - jqueryDiv.append(label); - }); - } - - _makeEmptyStyleFunction() { - const emptyStyle = _.assign({}, EMPTY_STYLE, { - weight: this._lineWeight, - }); - - return () => { - return emptyStyle; - }; - } - - _makeChoroplethStyler() { - const emptyStyle = this._makeEmptyStyleFunction(); - if (this._metrics.length === 0) { - return { - leafletStyleFunction: () => { - return emptyStyle(); - }, - getMismatches: () => { - return []; - }, - getLeafletBounds: () => { - return null; - }, - }; - } - - const { min, max } = getMinMax(this._metrics); - - // eslint-disable-next-line no-undef - const boundsOfAllFeatures = new this._leaflet.LatLngBounds(); - return { - leafletStyleFunction: (geojsonFeature) => { - const match = geojsonFeature.__kbnJoinedMetric; - if (!match) { - return emptyStyle(); - } - // eslint-disable-next-line no-undef - const boundsOfFeature = this._leaflet.geoJson(geojsonFeature).getBounds(); - boundsOfAllFeatures.extend(boundsOfFeature); - - return { - fillColor: getChoroplethColor(match.value, min, max, this._colorRamp), - weight: this._lineWeight, - opacity: 1, - color: 'white', - fillOpacity: 0.7, - }; - }, - /** - * should not be called until getLeafletStyleFunction has been called - * @return {Array} - */ - getMismatches: () => { - const mismatches = this._metrics.slice(); - this._sortedFeatures.forEach((feature) => { - const index = mismatches.indexOf(feature.__kbnJoinedMetric); - if (index >= 0) { - mismatches.splice(index, 1); - } - }); - return mismatches.map((b) => b.term); - }, - getLeafletBounds: function () { - return boundsOfAllFeatures.isValid() ? boundsOfAllFeatures : null; - }, - }; - } -} - -//lexicographic compare -function compareLexicographically(termA, termB) { - termA = typeof termA === 'string' ? termA : termA.toString(); - termB = typeof termB === 'string' ? termB : termB.toString(); - return termA.localeCompare(termB); -} - -function makeColorDarker(color) { - const amount = 1.3; //magic number, carry over from earlier - return d3.hcl(color).darker(amount).toString(); -} - -function getMinMax(data) { - let min = data[0].value; - let max = data[0].value; - for (let i = 1; i < data.length; i += 1) { - min = Math.min(data[i].value, min); - max = Math.max(data[i].value, max); - } - return { min, max }; -} - -function getChoroplethColor(value, min, max, colorRamp) { - if (min === max) { - return colorUtil.getColor(colorRamp, colorRamp.length - 1); - } - const fraction = (value - min) / (max - min); - const index = Math.round(colorRamp.length * fraction) - 1; - const i = Math.max(Math.min(colorRamp.length - 1, index), 0); - - return colorUtil.getColor(colorRamp, i); -} diff --git a/src/plugins/region_map/public/components/index.tsx b/src/plugins/region_map/public/components/index.tsx deleted file mode 100644 index 55e26ec311c41f..00000000000000 --- a/src/plugins/region_map/public/components/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { lazy } from 'react'; -import { IServiceSettings } from 'src/plugins/maps_ems/public'; -import { VisEditorOptionsProps } from 'src/plugins/visualizations/public'; -import { RegionMapVisParams } from '../region_map_types'; - -const RegionMapOptions = lazy(() => import('./region_map_options')); - -export const createRegionMapOptions = (getServiceSettings: () => Promise) => ( - props: VisEditorOptionsProps -) => ; diff --git a/src/plugins/region_map/public/components/region_map_options.tsx b/src/plugins/region_map/public/components/region_map_options.tsx deleted file mode 100644 index e3be8f9dcec098..00000000000000 --- a/src/plugins/region_map/public/components/region_map_options.tsx +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useCallback, useMemo } from 'react'; -import { EuiIcon, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { VisEditorOptionsProps } from 'src/plugins/visualizations/public'; -import { truncatedColorSchemas } from '../../../charts/public'; -import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_ems/public'; -import { SelectOption, SwitchOption, NumberInputOption } from '../../../vis_default_editor/public'; -import { WmsOptions } from '../../../maps_legacy/public'; -import { RegionMapVisParams } from '../region_map_types'; -import { getTmsLayers, getVectorLayers } from '../kibana_services'; - -const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({ - text: name, - value: layerId, -}); - -const mapFieldForOption = ({ description, name }: FileLayerField) => ({ - text: description, - value: name, -}); - -const tmsLayers = getTmsLayers(); -const vectorLayers = getVectorLayers(); -const vectorLayerOptions = vectorLayers.map(mapLayerForOption); - -export type RegionMapOptionsProps = { - getServiceSettings: () => Promise; -} & VisEditorOptionsProps; - -function RegionMapOptions(props: RegionMapOptionsProps) { - const { getServiceSettings, stateParams, setValue } = props; - const fieldOptions = useMemo( - () => - ((stateParams.selectedLayer && stateParams.selectedLayer.fields) || []).map( - mapFieldForOption - ), - [stateParams.selectedLayer] - ); - - const setEmsHotLink = useCallback( - async (layer: VectorLayer) => { - const serviceSettings = await getServiceSettings(); - const emsHotLink = await serviceSettings.getEMSHotLink(layer); - setValue('emsHotLink', emsHotLink); - }, - [setValue, getServiceSettings] - ); - - const setLayer = useCallback( - async (paramName: 'selectedLayer', value: VectorLayer['layerId']) => { - const newLayer = vectorLayers.find(({ layerId }: VectorLayer) => layerId === value); - - if (newLayer) { - setValue(paramName, newLayer); - setValue('selectedJoinField', newLayer.fields[0]); - setEmsHotLink(newLayer); - } - }, - [setEmsHotLink, setValue] - ); - - const setField = useCallback( - (paramName: 'selectedJoinField', value: FileLayerField['name']) => { - if (stateParams.selectedLayer) { - setValue( - paramName, - stateParams.selectedLayer.fields.find((f) => f.name === value) - ); - } - }, - [setValue, stateParams.selectedLayer] - ); - - return ( - <> - - -

- -

-
- - - - - {' '} - - - - ) - } - options={vectorLayerOptions} - paramName="selectedLayer" - value={stateParams.selectedLayer && stateParams.selectedLayer.layerId} - setValue={setLayer} - /> - - - - - - -
- - - - - -

- -

-
- - - - - -
- - - - - - ); -} - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export { RegionMapOptions as default }; diff --git a/src/plugins/region_map/public/get_deprecation_message.tsx b/src/plugins/region_map/public/get_deprecation_message.tsx deleted file mode 100644 index 2606c8ed108e23..00000000000000 --- a/src/plugins/region_map/public/get_deprecation_message.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { getQueryService, getShareService } from './kibana_services'; -import { Vis } from '../../visualizations/public'; -import { LegacyMapDeprecationMessage } from '../../maps_legacy/public'; - -function getEmsLayerId(id: string | number, layerId: string) { - if (typeof id === 'string') { - return id; - } - - // Region maps from 6.x will have numerical EMS id refering to S3 bucket id. - // In this case, use layerId with contains the EMS layer name. - const split = layerId.split('.'); - return split.length === 2 ? split[1] : undefined; -} - -export function getDeprecationMessage(vis: Vis) { - const title = i18n.translate('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }); - - async function onClick(e: React.MouseEvent) { - e.preventDefault(); - - const locator = getShareService().url.locators.get('MAPS_APP_REGION_MAP_LOCATOR'); - if (!locator) return; - - const query = getQueryService(); - const params: { [key: string]: any } = { - label: vis.title ? vis.title : title, - emsLayerId: vis.params.selectedLayer.isEMS - ? getEmsLayerId(vis.params.selectedLayer.id, vis.params.selectedLayer.layerId) - : undefined, - leftFieldName: vis.params.selectedLayer.isEMS ? vis.params.selectedJoinField.name : undefined, - colorSchema: vis.params.colorSchema, - indexPatternId: vis.data.indexPattern?.id, - indexPatternTitle: vis.data.indexPattern?.title, - metricAgg: 'count', - filters: query.filterManager.getFilters(), - query: query.queryString.getQuery(), - timeRange: query.timefilter.timefilter.getTime(), - }; - - const bucketAggs = vis.data?.aggs?.byType('buckets'); - if (bucketAggs?.length && bucketAggs[0].type.dslName === 'terms') { - params.termsFieldName = bucketAggs[0].getField()?.name; - params.termsSize = bucketAggs[0].getParam('size'); - } - - const metricAggs = vis.data?.aggs?.byType('metrics'); - if (metricAggs?.length) { - params.metricAgg = metricAggs[0].type.dslName; - params.metricFieldName = metricAggs[0].getField()?.name; - } - - locator.navigate(params); - } - - return ( - - ); -} diff --git a/src/plugins/region_map/public/index.ts b/src/plugins/region_map/public/index.ts deleted file mode 100644 index b2b0f38e6e96dc..00000000000000 --- a/src/plugins/region_map/public/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PluginInitializerContext } from 'kibana/public'; -import { RegionMapPlugin as Plugin } from './plugin'; - -export interface RegionMapsConfigType { - includeElasticMapsService: boolean; - layers: any[]; -} - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} diff --git a/src/plugins/region_map/public/kibana_services.ts b/src/plugins/region_map/public/kibana_services.ts deleted file mode 100644 index 030d021006d985..00000000000000 --- a/src/plugins/region_map/public/kibana_services.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CoreStart } from 'kibana/public'; -import { NotificationsStart } from 'kibana/public'; -import { createGetterSetter } from '../../kibana_utils/public'; -import { DataPublicPluginStart } from '../../data/public'; -import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { SharePluginStart } from '../../share/public'; -import { VectorLayer, TmsLayer } from '../../maps_ems/public'; - -export const [getCoreService, setCoreService] = createGetterSetter('Core'); - -export const [getFormatService, setFormatService] = createGetterSetter< - DataPublicPluginStart['fieldFormats'] ->('data.fieldFormats'); - -export const [getNotifications, setNotifications] = createGetterSetter( - 'Notifications' -); - -export const [getQueryService, setQueryService] = createGetterSetter< - DataPublicPluginStart['query'] ->('Query'); - -export const [getShareService, setShareService] = createGetterSetter('Share'); - -export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter( - 'KibanaLegacy' -); - -export const [getTmsLayers, setTmsLayers] = createGetterSetter('TmsLayers'); - -export const [getVectorLayers, setVectorLayers] = createGetterSetter('VectorLayers'); diff --git a/src/plugins/region_map/public/plugin.ts b/src/plugins/region_map/public/plugin.ts deleted file mode 100644 index 724404849aea8a..00000000000000 --- a/src/plugins/region_map/public/plugin.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, - IUiSettingsClient, - NotificationsStart, -} from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; -// @ts-ignore -import { createRegionMapFn } from './region_map_fn'; -// @ts-ignore -import { createRegionMapTypeDefinition } from './region_map_type'; -import { MapsLegacyPluginSetup } from '../../maps_legacy/public'; -import { IServiceSettings, MapsEmsPluginSetup } from '../../maps_ems/public'; -import { - setCoreService, - setFormatService, - setNotifications, - setKibanaLegacy, - setQueryService, - setShareService, -} from './kibana_services'; -import { DataPublicPluginStart } from '../../data/public'; -import { RegionMapsConfigType } from './index'; -import { MapsLegacyConfig } from '../../maps_legacy/config'; -import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { SharePluginStart } from '../../share/public'; -import { getRegionMapRenderer } from './region_map_renderer'; - -/** @private */ -export interface RegionMapVisualizationDependencies { - uiSettings: IUiSettingsClient; - regionmapsConfig: RegionMapsConfig; - getServiceSettings: () => Promise; - BaseMapsVisualization: any; -} - -/** @internal */ -export interface RegionMapPluginSetupDependencies { - expressions: ReturnType; - visualizations: VisualizationsSetup; - mapsLegacy: MapsLegacyPluginSetup; - mapsEms: MapsEmsPluginSetup; -} - -/** @internal */ -export interface RegionMapPluginStartDependencies { - data: DataPublicPluginStart; - notifications: NotificationsStart; - kibanaLegacy: KibanaLegacyStart; - share: SharePluginStart; -} - -/** @internal */ -export interface RegionMapsConfig { - includeElasticMapsService: boolean; - layers: any[]; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface RegionMapPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface RegionMapPluginStart {} - -/** @internal */ -export class RegionMapPlugin implements Plugin { - readonly _initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this._initializerContext = initializerContext; - } - - public setup( - core: CoreSetup, - { expressions, visualizations, mapsLegacy, mapsEms }: RegionMapPluginSetupDependencies - ) { - const config = { - ...this._initializerContext.config.get(), - // The maps legacy plugin updates the regionmap config directly in service_settings, - // future work on how configurations across the different plugins are organized would - // ideally constrain regionmap config updates to occur only from this plugin - ...mapsEms.config.regionmap, - }; - const visualizationDependencies: Readonly = { - uiSettings: core.uiSettings, - regionmapsConfig: config as RegionMapsConfig, - getServiceSettings: mapsEms.getServiceSettings, - BaseMapsVisualization: mapsLegacy.getBaseMapsVis(), - }; - - expressions.registerFunction(createRegionMapFn); - expressions.registerRenderer(getRegionMapRenderer(visualizationDependencies)); - - visualizations.createBaseVisualization( - createRegionMapTypeDefinition(visualizationDependencies) - ); - - return {}; - } - - public start(core: CoreStart, plugins: RegionMapPluginStartDependencies) { - setCoreService(core); - setFormatService(plugins.data.fieldFormats); - setQueryService(plugins.data.query); - setNotifications(core.notifications); - setKibanaLegacy(plugins.kibanaLegacy); - setShareService(plugins.share); - return {}; - } -} diff --git a/src/plugins/region_map/public/region_map_fn.test.ts b/src/plugins/region_map/public/region_map_fn.test.ts deleted file mode 100644 index ffb8f22f0aa1c1..00000000000000 --- a/src/plugins/region_map/public/region_map_fn.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; -import { createRegionMapFn } from './region_map_fn'; - -describe('interpreter/functions#regionmap', () => { - const fn = functionWrapper(createRegionMapFn()); - const context = { - type: 'datatable', - rows: [{ 'col-0-1': 0 }], - columns: [{ id: 'col-0-1', name: 'Count' }], - }; - const visConfig = { - legendPosition: 'bottomright', - addTooltip: true, - colorSchema: 'Yellow to Red', - emsHotLink: '', - selectedJoinField: null, - isDisplayWarning: true, - wms: { - enabled: false, - options: { - format: 'image/png', - transparent: true, - }, - }, - mapZoom: 2, - mapCenter: [0, 0], - outlineWeight: 1, - showAllShapes: true, - metric: { - accessor: 0, - format: { - id: 'number', - }, - params: {}, - aggType: 'count', - }, - }; - - it('returns an object with the correct structure', () => { - const actual = fn(context, { visConfig: JSON.stringify(visConfig) }); - expect(actual).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/region_map/public/region_map_fn.ts b/src/plugins/region_map/public/region_map_fn.ts deleted file mode 100644 index df3471bc920511..00000000000000 --- a/src/plugins/region_map/public/region_map_fn.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -import type { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; -import { RegionMapVisConfig } from './region_map_types'; - -interface Arguments { - visConfig: string | null; -} - -export interface RegionMapVisRenderValue { - visData: Datatable; - visType: 'region_map'; - visConfig: RegionMapVisConfig; -} - -export type RegionMapExpressionFunctionDefinition = ExpressionFunctionDefinition< - 'regionmap', - Datatable, - Arguments, - Render ->; - -export const createRegionMapFn = (): RegionMapExpressionFunctionDefinition => ({ - name: 'regionmap', - type: 'render', - context: { - types: ['datatable'], - }, - help: i18n.translate('regionMap.function.help', { - defaultMessage: 'Regionmap visualization', - }), - args: { - visConfig: { - types: ['string', 'null'], - default: '"{}"', - help: '', - }, - }, - fn(context, args, handlers) { - const visConfig = args.visConfig && JSON.parse(args.visConfig); - - if (handlers?.inspectorAdapters?.tables) { - handlers.inspectorAdapters.tables.logDatatable('default', context); - } - return { - type: 'render', - as: 'region_map_vis', - value: { - visData: context, - visType: 'region_map', - visConfig, - }, - }; - }, -}); diff --git a/src/plugins/region_map/public/region_map_renderer.tsx b/src/plugins/region_map/public/region_map_renderer.tsx deleted file mode 100644 index e74a2013daf3b8..00000000000000 --- a/src/plugins/region_map/public/region_map_renderer.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { lazy } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; - -import { ExpressionRenderDefinition } from 'src/plugins/expressions'; -import { VisualizationContainer } from '../../visualizations/public'; -import { RegionMapVisualizationDependencies } from './plugin'; -import { RegionMapVisRenderValue } from './region_map_fn'; - -const RegionMapVisualization = lazy(() => import('./region_map_visualization_component')); - -export const getRegionMapRenderer: ( - deps: RegionMapVisualizationDependencies -) => ExpressionRenderDefinition = (deps) => ({ - name: 'region_map_vis', - reuseDomNode: true, - render: async (domNode, { visConfig, visData }, handlers) => { - handlers.onDestroy(() => { - unmountComponentAtNode(domNode); - }); - - render( - - - , - domNode - ); - }, -}); diff --git a/src/plugins/region_map/public/region_map_type.ts b/src/plugins/region_map/public/region_map_type.ts deleted file mode 100644 index 5797812be34b08..00000000000000 --- a/src/plugins/region_map/public/region_map_type.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -import { VisTypeDefinition } from '../../visualizations/public'; -import { ORIGIN, VectorLayer } from '../../maps_ems/public'; - -import { getDeprecationMessage } from './get_deprecation_message'; -import { RegionMapVisualizationDependencies } from './plugin'; -import { createRegionMapOptions } from './components'; -import { toExpressionAst } from './to_ast'; -import { RegionMapVisParams } from './region_map_types'; -import { mapToLayerWithId } from './util'; -import { setTmsLayers, setVectorLayers } from './kibana_services'; - -export function createRegionMapTypeDefinition({ - uiSettings, - regionmapsConfig, - getServiceSettings, -}: RegionMapVisualizationDependencies): VisTypeDefinition { - return { - name: 'region_map', - getInfoMessage: getDeprecationMessage, - title: i18n.translate('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }), - description: i18n.translate('regionMap.mapVis.regionMapDescription', { - defaultMessage: - 'Show metrics on a thematic map. Use one of the \ -provided base maps, or add your own. Darker colors represent higher values.', - }), - icon: 'visMapRegion', - visConfig: { - defaults: { - legendPosition: 'bottomright', - addTooltip: true, - colorSchema: 'Yellow to Red', - emsHotLink: '', - isDisplayWarning: true, - wms: uiSettings.get('visualization:tileMap:WMSdefaults'), - mapZoom: 2, - mapCenter: [0, 0], - outlineWeight: 1, - showAllShapes: true, // still under consideration - }, - }, - editorConfig: { - optionsTemplate: createRegionMapOptions(getServiceSettings), - schemas: [ - { - group: 'metrics', - name: 'metric', - title: i18n.translate('regionMap.mapVis.regionMapEditorConfig.schemas.metricTitle', { - defaultMessage: 'Value', - }), - min: 1, - max: 1, - aggFilter: [ - 'count', - 'avg', - 'sum', - 'min', - 'max', - 'cardinality', - 'top_hits', - 'sum_bucket', - 'min_bucket', - 'max_bucket', - 'avg_bucket', - ], - defaults: [{ schema: 'metric', type: 'count' }], - }, - { - group: 'buckets', - name: 'segment', - title: i18n.translate('regionMap.mapVis.regionMapEditorConfig.schemas.segmentTitle', { - defaultMessage: 'Shape field', - }), - min: 1, - max: 1, - aggFilter: ['terms'], - }, - ], - }, - toExpressionAst, - setup: async (vis) => { - const serviceSettings = await getServiceSettings(); - const tmsLayers = await serviceSettings.getTMSServices(); - setTmsLayers(tmsLayers); - setVectorLayers([]); - - if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) { - vis.params.wms.selectedTmsLayer = tmsLayers[0]; - } - - const vectorLayers = regionmapsConfig.layers.map( - mapToLayerWithId.bind(null, ORIGIN.KIBANA_YML) - ); - let selectedLayer = vectorLayers[0]; - let selectedJoinField = selectedLayer ? selectedLayer.fields[0] : undefined; - if (regionmapsConfig.includeElasticMapsService) { - const layers = await serviceSettings.getFileLayers(); - const newLayers = layers - .map(mapToLayerWithId.bind(null, ORIGIN.EMS)) - .filter( - (layer: VectorLayer) => - !vectorLayers.some((vectorLayer) => vectorLayer.layerId === layer.layerId) - ); - - // backfill v1 manifest for now - newLayers.forEach((layer: VectorLayer) => { - if (layer.format === 'geojson') { - layer.format = { - type: 'geojson', - }; - } - }); - - const allVectorLayers = [...vectorLayers, ...newLayers]; - setVectorLayers(allVectorLayers); - - [selectedLayer] = allVectorLayers; - selectedJoinField = selectedLayer ? selectedLayer.fields[0] : undefined; - - if (selectedLayer && !vis.params.selectedLayer && selectedLayer.isEMS) { - vis.params.emsHotLink = await serviceSettings.getEMSHotLink(selectedLayer); - } - } - - if (!vis.params.selectedLayer) { - vis.params.selectedLayer = selectedLayer; - vis.params.selectedJoinField = selectedJoinField; - } - - return vis; - }, - requiresSearch: true, - }; -} diff --git a/src/plugins/region_map/public/region_map_types.ts b/src/plugins/region_map/public/region_map_types.ts deleted file mode 100644 index 0a9235f1c82df0..00000000000000 --- a/src/plugins/region_map/public/region_map_types.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SchemaConfig } from 'src/plugins/visualizations/public'; -import { VectorLayer, FileLayerField } from '../../maps_ems/public'; -import { WMSOptions } from '../../maps_legacy/public'; - -export interface RegionMapVisParams { - readonly addTooltip: true; - readonly legendPosition: 'bottomright'; - colorSchema: string; - emsHotLink?: string | null; - mapCenter: [number, number]; - mapZoom: number; - outlineWeight: number | ''; - isDisplayWarning: boolean; - showAllShapes: boolean; - selectedLayer?: VectorLayer; - selectedJoinField?: FileLayerField; - wms: WMSOptions; -} - -export interface RegionMapVisConfig extends RegionMapVisParams { - metric: SchemaConfig; - bucket?: SchemaConfig; -} diff --git a/src/plugins/region_map/public/region_map_visualization.js b/src/plugins/region_map/public/region_map_visualization.js deleted file mode 100644 index 80bee417009a4e..00000000000000 --- a/src/plugins/region_map/public/region_map_visualization.js +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { getFormatService, getNotifications, getKibanaLegacy } from './kibana_services'; -import { truncatedColorMaps } from '../../charts/public'; -import { tooltipFormatter } from './tooltip_formatter'; -import { mapTooltipProvider, lazyLoadMapsLegacyModules } from '../../maps_legacy/public'; -import { ORIGIN } from '../../maps_ems/public'; - -export function createRegionMapVisualization({ - regionmapsConfig, - uiSettings, - BaseMapsVisualization, - getServiceSettings, -}) { - return class RegionMapsVisualization extends BaseMapsVisualization { - constructor(container, handlers, initialVisParams) { - super(container, handlers, initialVisParams); - this._choroplethLayer = null; - this._tooltipFormatter = mapTooltipProvider(container, tooltipFormatter); - } - - async render(esResponse, visParams) { - getKibanaLegacy().loadFontAwesome(); - await super.render(esResponse, visParams); - if (this._choroplethLayer) { - await this._choroplethLayer.whenDataLoaded(); - } - } - - async _updateData(table) { - this._chartData = table; - const termColumn = this._params.bucket ? table.columns[this._params.bucket.accessor] : null; - const valueColumn = table.columns[this._params.metric.accessor]; - let results; - if (!this._hasColumns() || !table.rows.length) { - results = []; - } else { - results = table.rows.map((row) => { - const term = row[termColumn.id]; - const value = row[valueColumn.id]; - return { term: term, value: value }; - }); - } - - const selectedLayer = await this._loadConfig(this._params.selectedLayer); - if (!this._params.selectedJoinField && selectedLayer) { - this._params.selectedJoinField = selectedLayer.fields[0]; - } - - if (!selectedLayer) { - return; - } - - await this._updateChoroplethLayerForNewMetrics( - selectedLayer.name, - selectedLayer.attribution, - this._params.showAllShapes, - results - ); - - const metricFieldFormatter = getFormatService().deserialize(this._params.metric.format); - - this._choroplethLayer.setMetrics(results, metricFieldFormatter, valueColumn.name); - if (termColumn && valueColumn) { - this._choroplethLayer.setTooltipFormatter( - this._tooltipFormatter, - metricFieldFormatter, - termColumn.name, - valueColumn.name - ); - } - - this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState); - } - - async _loadConfig(fileLayerConfig) { - // Load the selected layer from the metadata-service. - // Do not use the selectedLayer from the visState. - // These settings are stored in the URL and can be used to inject dirty display content. - - const { escape } = await import('lodash'); - - if ( - fileLayerConfig.isEMS || //Hosted by EMS. Metadata needs to be resolved through EMS - (fileLayerConfig.layerId && fileLayerConfig.layerId.startsWith(`${ORIGIN.EMS}.`)) //fallback for older saved objects - ) { - const serviceSettings = await getServiceSettings(); - return await serviceSettings.loadFileLayerConfig(fileLayerConfig); - } - - //Configured in the kibana.yml. Needs to be resolved through the settings. - const configuredLayer = regionmapsConfig.layers.find( - (layer) => layer.name === fileLayerConfig.name - ); - - if (configuredLayer) { - return { - ...configuredLayer, - attribution: escape(configuredLayer.attribution ? configuredLayer.attribution : ''), - }; - } - - return null; - } - - async _updateParams() { - await super._updateParams(); - - const selectedLayer = await this._loadConfig(this._params.selectedLayer); - - if (!this._params.selectedJoinField && selectedLayer) { - this._params.selectedJoinField = selectedLayer.fields[0]; - } - - if (!this._params.selectedJoinField || !selectedLayer) { - return; - } - - await this._updateChoroplethLayerForNewProperties( - selectedLayer.name, - selectedLayer.attribution, - this._params.showAllShapes - ); - - const metricFieldFormatter = getFormatService().deserialize(this._params.metric.format); - - this._choroplethLayer.setJoinField(this._params.selectedJoinField.name); - this._choroplethLayer.setColorRamp(truncatedColorMaps[this._params.colorSchema].value); - this._choroplethLayer.setLineWeight(this._params.outlineWeight); - this._choroplethLayer.setTooltipFormatter( - this._tooltipFormatter, - metricFieldFormatter, - this._metricLabel - ); - } - - async _updateChoroplethLayerForNewMetrics(name, attribution, showAllData, newMetrics) { - if ( - this._choroplethLayer && - this._choroplethLayer.canReuseInstanceForNewMetrics(name, showAllData, newMetrics) - ) { - return; - } - await this._recreateChoroplethLayer(name, attribution, showAllData); - } - - async _updateChoroplethLayerForNewProperties(name, attribution, showAllData) { - if (this._choroplethLayer && this._choroplethLayer.canReuseInstance(name, showAllData)) { - return; - } - await this._recreateChoroplethLayer(name, attribution, showAllData); - } - - async _recreateChoroplethLayer(name, attribution, showAllData) { - const selectedLayer = await this._loadConfig(this._params.selectedLayer); - this._kibanaMap.removeLayer(this._choroplethLayer); - - if (this._choroplethLayer) { - this._choroplethLayer = this._choroplethLayer.cloneChoroplethLayerForNewData( - name, - attribution, - selectedLayer.format, - showAllData, - selectedLayer.meta, - selectedLayer, - await getServiceSettings(), - (await lazyLoadMapsLegacyModules()).L - ); - } else { - const { ChoroplethLayer } = await import('./choropleth_layer'); - this._choroplethLayer = new ChoroplethLayer( - name, - attribution, - selectedLayer.format, - showAllData, - selectedLayer.meta, - selectedLayer, - await getServiceSettings(), - (await lazyLoadMapsLegacyModules()).L - ); - } - - this._choroplethLayer.on('select', (event) => { - const { rows, columns } = this._chartData; - const rowIndex = rows.findIndex((row) => row[columns[0].id] === event); - this.handlers.event({ - name: 'filterBucket', - data: { - data: [ - { - table: this._chartData, - column: 0, - row: rowIndex, - value: event, - }, - ], - }, - }); - }); - - this._choroplethLayer.on('styleChanged', (event) => { - const shouldShowWarning = - this._params.isDisplayWarning && uiSettings.get('visualization:regionmap:showWarnings'); - if (event.mismatches.length > 0 && shouldShowWarning) { - getNotifications().toasts.addWarning({ - title: i18n.translate('regionMap.visualization.unableToShowMismatchesWarningTitle', { - defaultMessage: - 'Unable to show {mismatchesLength} {oneMismatch, plural, one {result} other {results}} on map', - values: { - mismatchesLength: event.mismatches.length, - oneMismatch: event.mismatches.length > 1 ? 0 : 1, - }, - }), - text: i18n.translate('regionMap.visualization.unableToShowMismatchesWarningText', { - defaultMessage: - "Ensure that each of these term matches a shape on that shape's join field: {mismatches}", - values: { - mismatches: event.mismatches ? event.mismatches.join(', ') : '', - }, - }), - }); - } - }); - - this._kibanaMap.addLayer(this._choroplethLayer); - } - - _hasColumns() { - return this._chartData && this._chartData.columns.length === 2; - } - }; -} diff --git a/src/plugins/region_map/public/region_map_visualization.scss b/src/plugins/region_map/public/region_map_visualization.scss deleted file mode 100644 index ee593e2fc9c8c8..00000000000000 --- a/src/plugins/region_map/public/region_map_visualization.scss +++ /dev/null @@ -1,4 +0,0 @@ -.rgmChart__wrapper, .rgmChart { - flex: 1 1 0; - display: flex; -} diff --git a/src/plugins/region_map/public/region_map_visualization_component.tsx b/src/plugins/region_map/public/region_map_visualization_component.tsx deleted file mode 100644 index cab2294475982f..00000000000000 --- a/src/plugins/region_map/public/region_map_visualization_component.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useEffect, useMemo, useRef } from 'react'; -import { EuiResizeObserver } from '@elastic/eui'; -import { throttle } from 'lodash'; - -import { IInterpreterRenderHandlers, Datatable } from 'src/plugins/expressions'; -import { PersistedState } from 'src/plugins/visualizations/public'; -import { RegionMapVisualizationDependencies } from './plugin'; -import { RegionMapVisConfig } from './region_map_types'; -// @ts-expect-error -import { createRegionMapVisualization } from './region_map_visualization'; - -import './region_map_visualization.scss'; - -interface RegionMapVisController { - render(visData?: Datatable, visConfig?: RegionMapVisConfig): Promise; - resize(): void; - destroy(): void; -} - -interface TileMapVisualizationProps { - deps: RegionMapVisualizationDependencies; - handlers: IInterpreterRenderHandlers; - visData: Datatable; - visConfig: RegionMapVisConfig; -} - -const RegionMapVisualization = ({ - deps, - handlers, - visData, - visConfig, -}: TileMapVisualizationProps) => { - const chartDiv = useRef(null); - const visController = useRef(null); - const isFirstRender = useRef(true); - const uiState = handlers.uiState as PersistedState | undefined; - - useEffect(() => { - if (chartDiv.current && isFirstRender.current) { - isFirstRender.current = false; - const Controller = createRegionMapVisualization(deps); - visController.current = new Controller(chartDiv.current, handlers, visConfig); - } - }, [deps, handlers, visConfig, visData]); - - useEffect(() => { - visController.current?.render(visData, visConfig).then(handlers.done); - }, [visData, visConfig, handlers.done]); - - useEffect(() => { - const onUiStateChange = () => { - visController.current?.render().then(handlers.done); - }; - - uiState?.on('change', onUiStateChange); - - return () => { - uiState?.off('change', onUiStateChange); - }; - }, [uiState, handlers.done]); - - useEffect(() => { - return () => { - visController.current?.destroy(); - visController.current = null; - }; - }, []); - - const updateChartSize = useMemo(() => throttle(() => visController.current?.resize(), 300), []); - - return ( - - {(resizeRef) => ( -
-
-
- )} - - ); -}; - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export { RegionMapVisualization as default }; diff --git a/src/plugins/region_map/public/to_ast.ts b/src/plugins/region_map/public/to_ast.ts deleted file mode 100644 index b68731cab30bb9..00000000000000 --- a/src/plugins/region_map/public/to_ast.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - EsaggsExpressionFunctionDefinition, - IndexPatternLoadExpressionFunctionDefinition, -} from '../../data/public'; -import { buildExpression, buildExpressionFunction } from '../../expressions/public'; -import { getVisSchemas, VisToExpressionAst } from '../../visualizations/public'; -import { RegionMapExpressionFunctionDefinition } from './region_map_fn'; -import { RegionMapVisConfig, RegionMapVisParams } from './region_map_types'; - -export const toExpressionAst: VisToExpressionAst = (vis, params) => { - const esaggs = buildExpressionFunction('esaggs', { - index: buildExpression([ - buildExpressionFunction('indexPatternLoad', { - id: vis.data.indexPattern!.id!, - }), - ]), - metricsAtAllLevels: false, - partialRows: false, - aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())), - }); - - const schemas = getVisSchemas(vis, params); - - const visConfig: RegionMapVisConfig = { - ...vis.params, - metric: schemas.metric[0], - }; - - if (schemas.segment) { - visConfig.bucket = schemas.segment[0]; - } - - const regionmap = buildExpressionFunction('regionmap', { - visConfig: JSON.stringify(visConfig), - }); - - const ast = buildExpression([esaggs, regionmap]); - - return ast.toAst(); -}; diff --git a/src/plugins/region_map/public/tooltip_formatter.js b/src/plugins/region_map/public/tooltip_formatter.js deleted file mode 100644 index 101d49b9d88b83..00000000000000 --- a/src/plugins/region_map/public/tooltip_formatter.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function tooltipFormatter(metric, fieldFormatter, fieldName, metricName) { - if (!metric) { - return ''; - } - - const details = []; - if (fieldName && metric) { - details.push({ - label: fieldName, - value: metric.term, - }); - } - - if (metric) { - details.push({ - label: metricName, - value: fieldFormatter ? fieldFormatter.convert(metric.value, 'text') : metric.value, - }); - } - return details; -} diff --git a/src/plugins/region_map/public/util.ts b/src/plugins/region_map/public/util.ts deleted file mode 100644 index 8e15a72b0365d3..00000000000000 --- a/src/plugins/region_map/public/util.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { FileLayer, VectorLayer, ORIGIN } from '../../maps_ems/public'; - -export const mapToLayerWithId = (prefix: string, layer: FileLayer): VectorLayer => ({ - ...layer, - layerId: `${prefix}.${layer.name}`, - isEMS: ORIGIN.EMS === prefix, -}); diff --git a/src/plugins/region_map/server/index.ts b/src/plugins/region_map/server/index.ts deleted file mode 100644 index eb185c773458b8..00000000000000 --- a/src/plugins/region_map/server/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CoreSetup } from 'src/core/server'; -import { getUiSettings } from './ui_settings'; - -export const plugin = () => ({ - setup(core: CoreSetup) { - core.uiSettings.register(getUiSettings()); - }, - - start() {}, -}); diff --git a/src/plugins/region_map/server/ui_settings.ts b/src/plugins/region_map/server/ui_settings.ts deleted file mode 100644 index 30b48064703c2f..00000000000000 --- a/src/plugins/region_map/server/ui_settings.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { UiSettingsParams } from 'kibana/server'; -import { schema } from '@kbn/config-schema'; - -export function getUiSettings(): Record> { - return { - 'visualization:regionmap:showWarnings': { - name: i18n.translate('regionMap.advancedSettings.visualization.showRegionMapWarningsTitle', { - defaultMessage: 'Show region map warning', - }), - value: true, - description: i18n.translate( - 'regionMap.advancedSettings.visualization.showRegionMapWarningsText', - { - defaultMessage: - 'Whether the region map shows a warning when terms cannot be joined to a shape on the map.', - } - ), - schema: schema.boolean(), - category: ['visualization'], - }, - }; -} diff --git a/src/plugins/region_map/tsconfig.json b/src/plugins/region_map/tsconfig.json deleted file mode 100644 index fec191402f2ab3..00000000000000 --- a/src/plugins/region_map/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./target/types", - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": ["public/**/*", "server/**/*"], - "references": [ - { "path": "../maps_legacy/tsconfig.json" }, - { "path": "../maps_ems/tsconfig.json" }, - { "path": "../vis_default_editor/tsconfig.json" }, - ] -} diff --git a/src/plugins/tile_map/README.md b/src/plugins/tile_map/README.md deleted file mode 100644 index 633ee7dba46d67..00000000000000 --- a/src/plugins/tile_map/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Coordinate map visualization - -Create a coordinate map. Display the results of a geohash_tile aggregation as bubbles, rectangles, or heatmap color blobs. - -This plugin is targeted for removal in 8.0. \ No newline at end of file diff --git a/src/plugins/tile_map/jest.config.js b/src/plugins/tile_map/jest.config.js deleted file mode 100644 index 7c51c61eca2a4e..00000000000000 --- a/src/plugins/tile_map/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/src/plugins/tile_map'], -}; diff --git a/src/plugins/tile_map/kibana.json b/src/plugins/tile_map/kibana.json deleted file mode 100644 index 48ed613b72cd34..00000000000000 --- a/src/plugins/tile_map/kibana.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id": "tileMap", - "owner": { - "name": "GIS", - "githubTeam": "kibana-gis" - }, - "version": "8.0.0", - "kibanaVersion": "kibana", - "ui": true, - "server": true, - "requiredPlugins": [ - "visualizations", - "expressions", - "mapsLegacy", - "mapsEms", - "kibanaLegacy", - "data", - "share" - ], - "requiredBundles": ["kibanaUtils", "charts", "visDefaultEditor"] -} diff --git a/src/plugins/tile_map/package.json b/src/plugins/tile_map/package.json deleted file mode 100644 index d9d0359f660489..00000000000000 --- a/src/plugins/tile_map/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "tile_map", - "version": "kibana" -} \ No newline at end of file diff --git a/src/plugins/tile_map/public/__snapshots__/tile_map_fn.test.ts.snap b/src/plugins/tile_map/public/__snapshots__/tile_map_fn.test.ts.snap deleted file mode 100644 index 7aab8b02890c05..00000000000000 --- a/src/plugins/tile_map/public/__snapshots__/tile_map_fn.test.ts.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`interpreter/functions#tilemap returns an object with the correct structure 1`] = ` -Object { - "as": "tile_map_vis", - "type": "render", - "value": Object { - "visConfig": Object { - "addTooltip": true, - "colorSchema": "Yellow to Red", - "dimensions": Object { - "geocentroid": null, - "geohash": null, - "metric": Object { - "accessor": 0, - "aggType": "count", - "format": Object { - "id": "number", - }, - "params": Object {}, - }, - }, - "heatClusterSize": 1.5, - "isDesaturated": true, - "legendPosition": "bottomright", - "mapCenter": Array [ - 0, - 0, - ], - "mapType": "Scaled Circle Markers", - "mapZoom": 2, - "wms": Object { - "enabled": false, - "options": Object { - "format": "image/png", - "transparent": true, - }, - }, - }, - "visData": Object { - "featureCollection": Object { - "features": Array [], - "type": "FeatureCollection", - }, - "meta": Object { - "geohashGridDimensionsAtEquator": null, - "geohashPrecision": null, - "max": null, - "min": null, - }, - }, - "visType": "tile_map", - }, -} -`; diff --git a/src/plugins/tile_map/public/components/collections.ts b/src/plugins/tile_map/public/components/collections.ts deleted file mode 100644 index f75d83c4a055f9..00000000000000 --- a/src/plugins/tile_map/public/components/collections.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { MapTypes } from '../utils/map_types'; - -export const collections = { - mapTypes: [ - { - value: MapTypes.ScaledCircleMarkers, - text: i18n.translate('tileMap.mapTypes.scaledCircleMarkersText', { - defaultMessage: 'Scaled circle markers', - }), - }, - { - value: MapTypes.ShadedCircleMarkers, - text: i18n.translate('tileMap.mapTypes.shadedCircleMarkersText', { - defaultMessage: 'Shaded circle markers', - }), - }, - { - value: MapTypes.ShadedGeohashGrid, - text: i18n.translate('tileMap.mapTypes.shadedGeohashGridText', { - defaultMessage: 'Shaded geohash grid', - }), - }, - { - value: MapTypes.Heatmap, - text: i18n.translate('tileMap.mapTypes.heatmapText', { - defaultMessage: 'Heatmap', - }), - }, - ], - legendPositions: [ - { - value: 'bottomleft', - text: i18n.translate('tileMap.legendPositions.bottomLeftText', { - defaultMessage: 'Bottom left', - }), - }, - { - value: 'bottomright', - text: i18n.translate('tileMap.legendPositions.bottomRightText', { - defaultMessage: 'Bottom right', - }), - }, - { - value: 'topleft', - text: i18n.translate('tileMap.legendPositions.topLeftText', { - defaultMessage: 'Top left', - }), - }, - { - value: 'topright', - text: i18n.translate('tileMap.legendPositions.topRightText', { - defaultMessage: 'Top right', - }), - }, - ], -}; diff --git a/src/plugins/tile_map/public/components/index.tsx b/src/plugins/tile_map/public/components/index.tsx deleted file mode 100644 index 31c8faec2409db..00000000000000 --- a/src/plugins/tile_map/public/components/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { lazy } from 'react'; -import type { TileMapOptionsProps } from './tile_map_options'; - -const TileMapOptions = lazy(() => import('./tile_map_options')); - -export const TileMapOptionsLazy = (props: TileMapOptionsProps) => ; diff --git a/src/plugins/tile_map/public/components/tile_map_options.tsx b/src/plugins/tile_map/public/components/tile_map_options.tsx deleted file mode 100644 index dbe28f0e2c2dde..00000000000000 --- a/src/plugins/tile_map/public/components/tile_map_options.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useEffect } from 'react'; -import { EuiPanel, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { VisEditorOptionsProps } from 'src/plugins/visualizations/public'; -import { - BasicOptions, - SelectOption, - SwitchOption, - RangeOption, -} from '../../../vis_default_editor/public'; -import { truncatedColorSchemas } from '../../../charts/public'; -import { WmsOptions } from '../../../maps_legacy/public'; -import { TileMapVisParams } from '../types'; -import { MapTypes } from '../utils/map_types'; -import { getTmsLayers } from '../services'; -import { collections } from './collections'; - -export type TileMapOptionsProps = VisEditorOptionsProps; - -const tmsLayers = getTmsLayers(); - -function TileMapOptions(props: TileMapOptionsProps) { - const { stateParams, setValue, vis } = props; - - useEffect(() => { - if (!stateParams.mapType) { - setValue('mapType', collections.mapTypes[0].value); - } - }, [setValue, stateParams.mapType]); - - return ( - <> - - - - {stateParams.mapType === MapTypes.Heatmap ? ( - - ) : ( - - )} - - - - - - - - - - - ); -} - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export { TileMapOptions as default }; diff --git a/src/plugins/tile_map/public/css_filters.js b/src/plugins/tile_map/public/css_filters.js deleted file mode 100644 index c1d5e68d5273f6..00000000000000 --- a/src/plugins/tile_map/public/css_filters.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; - -/** - * just a place to put feature detection checks - */ -export const supportsCssFilters = (function () { - const e = document.createElement('img'); - const rules = ['webkitFilter', 'mozFilter', 'msFilter', 'filter']; - const test = 'grayscale(1)'; - - rules.forEach(function (rule) { - e.style[rule] = test; - }); - - document.body.appendChild(e); - const styles = window.getComputedStyle(e); - const can = _(styles).pick(rules).includes(test); - document.body.removeChild(e); - - return can; -})(); diff --git a/src/plugins/tile_map/public/geohash_layer.js b/src/plugins/tile_map/public/geohash_layer.js deleted file mode 100644 index e84a23c04056a3..00000000000000 --- a/src/plugins/tile_map/public/geohash_layer.js +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { min, isEqual } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { KibanaMapLayer } from '../../maps_legacy/public'; -import { HeatmapMarkers } from './markers/heatmap'; -import { ScaledCirclesMarkers } from './markers/scaled_circles'; -import { ShadedCirclesMarkers } from './markers/shaded_circles'; -import { GeohashGridMarkers } from './markers/geohash_grid'; -import { MapTypes } from './utils/map_types'; - -export class GeohashLayer extends KibanaMapLayer { - constructor(featureCollection, featureCollectionMetaData, options, zoom, kibanaMap, leaflet) { - super(); - - this._featureCollection = featureCollection; - this._featureCollectionMetaData = featureCollectionMetaData; - - this._geohashOptions = options; - this._zoom = zoom; - this._kibanaMap = kibanaMap; - this._leaflet = leaflet; - const geojson = this._leaflet.geoJson(this._featureCollection); - this._bounds = geojson.getBounds(); - this._createGeohashMarkers(); - this._lastBounds = null; - } - - _createGeohashMarkers() { - const markerOptions = { - isFilteredByCollar: this._geohashOptions.isFilteredByCollar, - valueFormatter: this._geohashOptions.valueFormatter, - tooltipFormatter: this._geohashOptions.tooltipFormatter, - label: this._geohashOptions.label, - colorRamp: this._geohashOptions.colorRamp, - }; - switch (this._geohashOptions.mapType) { - case MapTypes.ScaledCircleMarkers: - this._geohashMarkers = new ScaledCirclesMarkers( - this._featureCollection, - this._featureCollectionMetaData, - markerOptions, - this._zoom, - this._kibanaMap, - this._leaflet - ); - break; - case MapTypes.ShadedCircleMarkers: - this._geohashMarkers = new ShadedCirclesMarkers( - this._featureCollection, - this._featureCollectionMetaData, - markerOptions, - this._zoom, - this._kibanaMap, - this._leaflet - ); - break; - case MapTypes.ShadedGeohashGrid: - this._geohashMarkers = new GeohashGridMarkers( - this._featureCollection, - this._featureCollectionMetaData, - markerOptions, - this._zoom, - this._kibanaMap, - this._leaflet - ); - break; - case MapTypes.Heatmap: - let radius = 15; - if (this._featureCollectionMetaData.geohashGridDimensionsAtEquator) { - const minGridLength = min(this._featureCollectionMetaData.geohashGridDimensionsAtEquator); - const metersPerPixel = this._kibanaMap.getMetersPerPixel(); - radius = minGridLength / metersPerPixel / 2; - } - radius = radius * parseFloat(this._geohashOptions.heatmap.heatClusterSize); - this._geohashMarkers = new HeatmapMarkers( - this._featureCollection, - { - radius: radius, - blur: radius, - maxZoom: this._kibanaMap.getZoomLevel(), - minOpacity: 0.1, - tooltipFormatter: this._geohashOptions.tooltipFormatter, - }, - this._zoom, - this._featureCollectionMetaData.max, - this._leaflet - ); - break; - default: - throw new Error( - i18n.translate('tileMap.geohashLayer.mapTitle', { - defaultMessage: '{mapType} mapType not recognized', - values: { - mapType: this._geohashOptions.mapType, - }, - }) - ); - } - - this._geohashMarkers.on('showTooltip', (event) => this.emit('showTooltip', event)); - this._geohashMarkers.on('hideTooltip', (event) => this.emit('hideTooltip', event)); - this._leafletLayer = this._geohashMarkers.getLeafletLayer(); - } - - appendLegendContents(jqueryDiv) { - return this._geohashMarkers.appendLegendContents(jqueryDiv); - } - - movePointer(...args) { - this._geohashMarkers.movePointer(...args); - } - - async getBounds() { - if (this._geohashOptions.fetchBounds) { - const geoHashBounds = await this._geohashOptions.fetchBounds(); - if (geoHashBounds) { - const northEast = this._leaflet.latLng( - geoHashBounds.top_left.lat, - geoHashBounds.bottom_right.lon - ); - const southWest = this._leaflet.latLng( - geoHashBounds.bottom_right.lat, - geoHashBounds.top_left.lon - ); - return this._leaflet.latLngBounds(southWest, northEast); - } - } - - return this._bounds; - } - - updateExtent() { - // Client-side filtering is only enabled when server-side filter is not used - if (!this._geohashOptions.isFilteredByCollar) { - const bounds = this._kibanaMap.getLeafletBounds(); - if (!this._lastBounds || !this._lastBounds.equals(bounds)) { - //this removal is required to trigger the bounds filter again - this._kibanaMap.removeLayer(this); - this._createGeohashMarkers(); - this._kibanaMap.addLayer(this); - } - this._lastBounds = bounds; - } - } - - isReusable(options) { - if (isEqual(this._geohashOptions, options)) { - return true; - } - - //check if any impacts leaflet styler function - if (this._geohashOptions.colorRamp !== options.colorRamp) { - return false; - } else if (this._geohashOptions.mapType !== options.mapType) { - return false; - } else if ( - this._geohashOptions.mapType === 'Heatmap' && - !isEqual(this._geohashOptions.heatmap, options) - ) { - return false; - } else { - return true; - } - } -} diff --git a/src/plugins/tile_map/public/get_deprecation_message.tsx b/src/plugins/tile_map/public/get_deprecation_message.tsx deleted file mode 100644 index 6f71aa15b8a6b5..00000000000000 --- a/src/plugins/tile_map/public/get_deprecation_message.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { getQueryService, getShareService } from './services'; -import { indexPatterns } from '../../data/public'; -import { Vis } from '../../visualizations/public'; -import { LegacyMapDeprecationMessage } from '../../maps_legacy/public'; - -export function getDeprecationMessage(vis: Vis) { - const title = i18n.translate('tileMap.vis.mapTitle', { - defaultMessage: 'Coordinate Map', - }); - - async function onClick(e: React.MouseEvent) { - e.preventDefault(); - - const locator = getShareService().url.locators.get('MAPS_APP_TILE_MAP_LOCATOR'); - if (!locator) return; - - const query = getQueryService(); - const params: { [key: string]: any } = { - label: vis.title ? vis.title : title, - mapType: vis.params.mapType, - colorSchema: vis.params.colorSchema, - indexPatternId: vis.data.indexPattern?.id, - metricAgg: 'count', - filters: query.filterManager.getFilters(), - query: query.queryString.getQuery(), - timeRange: query.timefilter.timefilter.getTime(), - }; - - const bucketAggs = vis.data?.aggs?.byType('buckets'); - if (bucketAggs?.length && bucketAggs[0].type.dslName === 'geohash_grid') { - params.geoFieldName = bucketAggs[0].getField()?.name; - } else if (vis.data.indexPattern) { - // attempt to default to first geo point field when geohash is not configured yet - const geoField = vis.data.indexPattern.fields.find((field) => { - return ( - !indexPatterns.isNestedField(field) && field.aggregatable && field.type === 'geo_point' - ); - }); - if (geoField) { - params.geoFieldName = geoField.name; - } - } - - const metricAggs = vis.data?.aggs?.byType('metrics'); - if (metricAggs?.length) { - params.metricAgg = metricAggs[0].type.dslName; - params.metricFieldName = metricAggs[0].getField()?.name; - } - - locator.navigate(params); - } - - return ( - - ); -} diff --git a/src/plugins/tile_map/public/index.ts b/src/plugins/tile_map/public/index.ts deleted file mode 100644 index 3104dde0d5ac62..00000000000000 --- a/src/plugins/tile_map/public/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PluginInitializerContext } from 'kibana/public'; -import { TileMapPlugin as Plugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} diff --git a/src/plugins/tile_map/public/markers/geohash_grid.js b/src/plugins/tile_map/public/markers/geohash_grid.js deleted file mode 100644 index c8c327d7a8a618..00000000000000 --- a/src/plugins/tile_map/public/markers/geohash_grid.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ScaledCirclesMarkers } from './scaled_circles'; - -export class GeohashGridMarkers extends ScaledCirclesMarkers { - getMarkerFunction() { - return (feature) => { - const geohashRect = feature.properties.geohash_meta.rectangle; - // get bounds from northEast[3] and southWest[1] - // corners in geohash rectangle - const corners = [ - [geohashRect[3][0], geohashRect[3][1]], - [geohashRect[1][0], geohashRect[1][1]], - ]; - return this._leaflet.rectangle(corners); - }; - } -} diff --git a/src/plugins/tile_map/public/markers/heatmap.js b/src/plugins/tile_map/public/markers/heatmap.js deleted file mode 100644 index 5b5649b1e992c7..00000000000000 --- a/src/plugins/tile_map/public/markers/heatmap.js +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import d3 from 'd3'; -import { EventEmitter } from 'events'; - -/** - * Map overlay: canvas layer with leaflet.heat plugin - * - * @param map {Leaflet Object} - * @param geoJson {geoJson Object} - * @param params {Object} - */ -export class HeatmapMarkers extends EventEmitter { - constructor(featureCollection, options, zoom, max, leaflet) { - super(); - this._geojsonFeatureCollection = featureCollection; - const points = dataToHeatArray(featureCollection, max); - this._leafletLayer = new leaflet.HeatLayer(points, options); - this._tooltipFormatter = options.tooltipFormatter; - this._zoom = zoom; - this._disableTooltips = false; - this._getLatLng = _.memoize( - function (feature) { - return leaflet.latLng(feature.geometry.coordinates[1], feature.geometry.coordinates[0]); - }, - function (feature) { - // turn coords into a string for the memoize cache - return [feature.geometry.coordinates[1], feature.geometry.coordinates[0]].join(','); - } - ); - this._addTooltips(); - } - - getBounds() { - return this._leafletLayer.getBounds(); - } - - getLeafletLayer() { - return this._leafletLayer; - } - - appendLegendContents() {} - - movePointer(type, event) { - if (type === 'mousemove') { - this._debounceMoveMoveLocation(event); - } else if (type === 'mouseout') { - this.emit('hideTooltip'); - } else if (type === 'mousedown') { - this._disableTooltips = true; - this.emit('hideTooltip'); - } else if (type === 'mouseup') { - this._disableTooltips = false; - } - } - - _addTooltips() { - const mouseMoveLocation = (e) => { - if (!this._geojsonFeatureCollection.features.length || this._disableTooltips) { - this.emit('hideTooltip'); - return; - } - - const feature = this._nearestFeature(e.latlng); - if (this._tooltipProximity(e.latlng, feature)) { - const content = this._tooltipFormatter(feature); - if (!content) { - return; - } - this.emit('showTooltip', { - content: content, - position: e.latlng, - }); - } else { - this.emit('hideTooltip'); - } - }; - - this._debounceMoveMoveLocation = _.debounce(mouseMoveLocation.bind(this), 15, { - leading: true, - trailing: false, - }); - } - - /** - * Finds nearest feature in mapData to event latlng - * - * @method _nearestFeature - * @param latLng {Leaflet latLng} - * @return nearestPoint {Leaflet latLng} - */ - _nearestFeature(latLng) { - const self = this; - let nearest; - - if (latLng.lng < -180 || latLng.lng > 180) { - return; - } - - _.reduce( - this._geojsonFeatureCollection.features, - function (distance, feature) { - const featureLatLng = self._getLatLng(feature); - const dist = latLng.distanceTo(featureLatLng); - - if (dist < distance) { - nearest = feature; - return dist; - } - - return distance; - }, - Infinity - ); - - return nearest; - } - - /** - * display tooltip if feature is close enough to event latlng - * - * @method _tooltipProximity - * @param latlng {Leaflet latLng Object} - * @param feature {geoJson Object} - * @return {Boolean} - */ - _tooltipProximity(latlng, feature) { - if (!feature) return; - - let showTip = false; - const featureLatLng = this._getLatLng(feature); - - // zoomScale takes map zoom and returns proximity value for tooltip display - // domain (input values) is map zoom (min 1 and max 18) - // range (output values) is distance in meters - // used to compare proximity of event latlng to feature latlng - const zoomScale = d3.scale - .linear() - .domain([1, 4, 7, 10, 13, 16, 18]) - .range([1000000, 300000, 100000, 15000, 2000, 150, 50]); - - const proximity = zoomScale(this._zoom); - const distance = latlng.distanceTo(featureLatLng); - - // maxLngDif is max difference in longitudes - // to prevent feature tooltip from appearing 360° - // away from event latlng - const maxLngDif = 40; - const lngDif = Math.abs(latlng.lng - featureLatLng.lng); - - if (distance < proximity && lngDif < maxLngDif) { - showTip = true; - } - - d3.scale.pow().exponent(0.2).domain([1, 18]).range([1500000, 50]); - return showTip; - } -} - -/** - * returns normalized data for heat map intensity - * - * @method dataToHeatArray - * @param featureCollection {Array} - * @return {Array} - */ -function dataToHeatArray(featureCollection, max) { - return featureCollection.features.map((feature) => { - const lat = feature.geometry.coordinates[1]; - const lng = feature.geometry.coordinates[0]; - // show bucket value normalized to max value - const heatIntensity = feature.properties.value / max; - - return [lat, lng, heatIntensity]; - }); -} diff --git a/src/plugins/tile_map/public/markers/scaled_circles.js b/src/plugins/tile_map/public/markers/scaled_circles.js deleted file mode 100644 index 54fba959532556..00000000000000 --- a/src/plugins/tile_map/public/markers/scaled_circles.js +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import d3 from 'd3'; -import $ from 'jquery'; -import { EventEmitter } from 'events'; -import { colorUtil } from '../../../maps_legacy/public'; -import { truncatedColorMaps } from '../../../charts/public'; - -export class ScaledCirclesMarkers extends EventEmitter { - constructor( - featureCollection, - featureCollectionMetaData, - options, - targetZoom, - kibanaMap, - leaflet - ) { - super(); - this._featureCollection = featureCollection; - this._featureCollectionMetaData = featureCollectionMetaData; - - this._zoom = targetZoom; - - this._valueFormatter = - options.valueFormatter || - ((x) => { - x; - }); - this._tooltipFormatter = - options.tooltipFormatter || - ((x) => { - x; - }); - this._label = options.label; - this._colorRamp = options.colorRamp; - - this._legendColors = null; - this._legendQuantizer = null; - this._leaflet = leaflet; - - this._popups = []; - - const layerOptions = { - pointToLayer: this.getMarkerFunction(), - style: this.getStyleFunction(), - onEachFeature: (feature, layer) => { - this._bindPopup(feature, layer); - }, - }; - // Filter leafletlayer on client when results are not filtered on the server - if (!options.isFilteredByCollar) { - layerOptions.filter = (feature) => { - const bucketRectBounds = feature.properties.geohash_meta.rectangle; - return kibanaMap.isInside(bucketRectBounds); - }; - } - this._leafletLayer = this._leaflet.geoJson(null, layerOptions); - this._leafletLayer.addData(this._featureCollection); - } - - getLeafletLayer() { - return this._leafletLayer; - } - - getStyleFunction() { - const min = _.get(this._featureCollectionMetaData, 'min', 0); - const max = _.get(this._featureCollectionMetaData, 'max', 1); - - const quantizeDomain = min !== max ? [min, max] : d3.scale.quantize().domain(); - - this._legendColors = this.getLegendColors(); - this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors); - - return makeStyleFunction(this._legendColors, quantizeDomain); - } - - movePointer() {} - - getLabel() { - if (this._popups.length) { - return this._label; - } - return ''; - } - - appendLegendContents(jqueryDiv) { - if (!this._legendColors || !this._legendQuantizer) { - return; - } - - const titleText = this.getLabel(); - const $title = $('
').addClass('visMapLegend__title').text(titleText); - jqueryDiv.append($title); - - this._legendColors.forEach((color) => { - const labelText = this._legendQuantizer - .invertExtent(color) - .map(this._valueFormatter) - .join(' – '); - - const label = $('
'); - const icon = $('').css({ - background: color, - 'border-color': makeColorDarker(color), - }); - - const text = $('').text(labelText); - label.append(icon); - label.append(text); - - jqueryDiv.append(label); - }); - } - - /** - * Binds popup and events to each feature on map - * - * @method bindPopup - * @param feature {Object} - * @param layer {Object} - * return {undefined} - */ - _bindPopup(feature, layer) { - const popup = layer.on({ - mouseover: (e) => { - const layer = e.target; - // bring layer to front if not older browser - if (!this._leaflet.Browser.ie && !this._leaflet.Browser.opera) { - layer.bringToFront(); - } - this._showTooltip(feature); - }, - mouseout: () => { - this.emit('hideTooltip'); - }, - }); - - this._popups.push(popup); - } - - /** - * Checks if event latlng is within bounds of mapData - * features and shows tooltip for that feature - * - * @method _showTooltip - * @param feature {LeafletFeature} - * @return undefined - */ - _showTooltip(feature) { - const content = this._tooltipFormatter(feature); - if (!content) { - return; - } - - const latLng = this._leaflet.latLng( - feature.geometry.coordinates[1], - feature.geometry.coordinates[0] - ); - this.emit('showTooltip', { - content: content, - position: latLng, - }); - } - - getMarkerFunction() { - const scaleFactor = 0.6; - return (feature, latlng) => { - const value = feature.properties.value; - const scaledRadius = this._radiusScale(value) * scaleFactor; - return this._leaflet.circleMarker(latlng).setRadius(scaledRadius); - }; - } - - /** - * radiusScale returns a number for scaled circle markers - * for relative sizing of markers - * - * @method _radiusScale - * @param value {Number} - * @return {Number} - */ - _radiusScale(value) { - //magic numbers - const precisionBiasBase = 5; - const precisionBiasNumerator = 200; - - const precision = _.max( - this._featureCollection.features.map((feature) => { - return String(feature.properties.geohash).length; - }) - ); - - const pct = Math.abs(value) / Math.abs(this._featureCollectionMetaData.max); - const zoomRadius = 0.5 * Math.pow(2, this._zoom); - const precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision); - - // square root value percentage - return Math.pow(pct, 0.5) * zoomRadius * precisionScale; - } - - getBounds() { - return this._leafletLayer.getBounds(); - } - - getLegendColors() { - const colorRamp = _.get(truncatedColorMaps[this._colorRamp], 'value'); - return colorUtil.getLegendColors(colorRamp); - } -} - -function makeColorDarker(color) { - const amount = 1.3; //magic number, carry over from earlier - return d3.hcl(color).darker(amount).toString(); -} - -function makeStyleFunction(legendColors, quantizeDomain) { - const legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(legendColors); - return (feature) => { - const value = _.get(feature, 'properties.value'); - const color = legendQuantizer(value); - return { - fillColor: color, - color: makeColorDarker(color), - weight: 1.5, - opacity: 1, - fillOpacity: 0.75, - }; - }; -} diff --git a/src/plugins/tile_map/public/markers/shaded_circles.js b/src/plugins/tile_map/public/markers/shaded_circles.js deleted file mode 100644 index a47fcd71427f33..00000000000000 --- a/src/plugins/tile_map/public/markers/shaded_circles.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import { ScaledCirclesMarkers } from './scaled_circles'; - -export class ShadedCirclesMarkers extends ScaledCirclesMarkers { - getMarkerFunction() { - // multiplier to reduce size of all circles - const scaleFactor = 0.8; - return (feature, latlng) => { - const radius = this._geohashMinDistance(feature) * scaleFactor; - return this._leaflet.circle(latlng, radius); - }; - } - - /** - * _geohashMinDistance returns a min distance in meters for sizing - * circle markers to fit within geohash grid rectangle - * - * @method _geohashMinDistance - * @param feature {Object} - * @return {Number} - */ - _geohashMinDistance(feature) { - const centerPoint = feature.properties.geohash_meta.center; - const geohashRect = feature.properties.geohash_meta.rectangle; - - // centerPoint is an array of [lat, lng] - // geohashRect is the 4 corners of the geoHash rectangle - // an array that starts at the southwest corner and proceeds - // clockwise, each value being an array of [lat, lng] - - // center lat and southeast lng - const east = this._leaflet.latLng([centerPoint[0], geohashRect[2][1]]); - // southwest lat and center lng - const north = this._leaflet.latLng([geohashRect[3][0], centerPoint[1]]); - - // get latLng of geohash center point - const center = this._leaflet.latLng([centerPoint[0], centerPoint[1]]); - - // get smallest radius at center of geohash grid rectangle - const eastRadius = Math.floor(center.distanceTo(east)); - const northRadius = Math.floor(center.distanceTo(north)); - return _.min([eastRadius, northRadius]); - } -} diff --git a/src/plugins/tile_map/public/plugin.ts b/src/plugins/tile_map/public/plugin.ts deleted file mode 100644 index 78cd12ffbccadc..00000000000000 --- a/src/plugins/tile_map/public/plugin.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, - IUiSettingsClient, -} from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; -import { MapsLegacyPluginSetup } from '../../maps_legacy/public'; -import { MapsEmsPluginSetup } from '../../maps_ems/public'; -import { IServiceSettings } from '../../maps_ems/public'; -import { DataPublicPluginStart } from '../../data/public'; -import { - setCoreService, - setFormatService, - setQueryService, - setKibanaLegacy, - setShareService, -} from './services'; -import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { SharePluginStart } from '../../share/public'; - -import { createTileMapFn } from './tile_map_fn'; -import { createTileMapTypeDefinition } from './tile_map_type'; -import { getTileMapRenderer } from './tile_map_renderer'; - -/** @private */ -export interface TileMapVisualizationDependencies { - uiSettings: IUiSettingsClient; - getZoomPrecision: any; - getPrecision: any; - BaseMapsVisualization: any; - getServiceSettings: () => Promise; -} - -/** @internal */ -export interface TileMapPluginSetupDependencies { - expressions: ReturnType; - visualizations: VisualizationsSetup; - mapsLegacy: MapsLegacyPluginSetup; - mapsEms: MapsEmsPluginSetup; -} - -/** @internal */ -export interface TileMapPluginStartDependencies { - data: DataPublicPluginStart; - kibanaLegacy: KibanaLegacyStart; - share: SharePluginStart; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface TileMapPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface TileMapPluginStart {} - -/** @internal */ -export class TileMapPlugin implements Plugin { - initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this.initializerContext = initializerContext; - } - - public setup( - core: CoreSetup, - { expressions, visualizations, mapsLegacy, mapsEms }: TileMapPluginSetupDependencies - ) { - const { getZoomPrecision, getPrecision } = mapsLegacy; - const visualizationDependencies: Readonly = { - getZoomPrecision, - getPrecision, - BaseMapsVisualization: mapsLegacy.getBaseMapsVis(), - uiSettings: core.uiSettings, - getServiceSettings: mapsEms.getServiceSettings, - }; - - expressions.registerFunction(createTileMapFn); - expressions.registerRenderer(getTileMapRenderer(visualizationDependencies)); - - visualizations.createBaseVisualization(createTileMapTypeDefinition(visualizationDependencies)); - - return {}; - } - - public start(core: CoreStart, plugins: TileMapPluginStartDependencies) { - setFormatService(plugins.data.fieldFormats); - setQueryService(plugins.data.query); - setKibanaLegacy(plugins.kibanaLegacy); - setShareService(plugins.share); - setCoreService(core); - return {}; - } -} diff --git a/src/plugins/tile_map/public/services.ts b/src/plugins/tile_map/public/services.ts deleted file mode 100644 index cef4fbae03f38a..00000000000000 --- a/src/plugins/tile_map/public/services.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CoreStart } from 'kibana/public'; -import { createGetterSetter } from '../../kibana_utils/public'; -import { DataPublicPluginStart } from '../../data/public'; -import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { SharePluginStart } from '../../share/public'; -import { TmsLayer } from '../../maps_ems/public'; - -export const [getCoreService, setCoreService] = createGetterSetter('Core'); - -export const [getFormatService, setFormatService] = createGetterSetter< - DataPublicPluginStart['fieldFormats'] ->('vislib data.fieldFormats'); - -export const [getQueryService, setQueryService] = createGetterSetter< - DataPublicPluginStart['query'] ->('Query'); - -export const [getShareService, setShareService] = createGetterSetter('Share'); - -export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter( - 'KibanaLegacy' -); - -export const [getTmsLayers, setTmsLayers] = createGetterSetter('TmsLayers'); diff --git a/src/plugins/tile_map/public/tile_map_fn.test.ts b/src/plugins/tile_map/public/tile_map_fn.test.ts deleted file mode 100644 index 7e8e065e959dd2..00000000000000 --- a/src/plugins/tile_map/public/tile_map_fn.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; -import { createTileMapFn } from './tile_map_fn'; - -jest.mock('./utils', () => ({ - convertToGeoJson: jest.fn().mockReturnValue({ - featureCollection: { - type: 'FeatureCollection', - features: [], - }, - meta: { - min: null, - max: null, - geohashPrecision: null, - geohashGridDimensionsAtEquator: null, - }, - }), -})); - -import { convertToGeoJson } from './utils'; - -describe('interpreter/functions#tilemap', () => { - const fn = functionWrapper(createTileMapFn()); - const context = { - type: 'datatable', - rows: [{ 'col-0-1': 0 }], - columns: [{ id: 'col-0-1', name: 'Count' }], - }; - const visConfig = { - colorSchema: 'Yellow to Red', - mapType: 'Scaled Circle Markers', - isDesaturated: true, - addTooltip: true, - heatClusterSize: 1.5, - legendPosition: 'bottomright', - mapZoom: 2, - mapCenter: [0, 0], - wms: { - enabled: false, - options: { - format: 'image/png', - transparent: true, - }, - }, - dimensions: { - metric: { - accessor: 0, - format: { - id: 'number', - }, - params: {}, - aggType: 'count', - }, - geohash: null, - geocentroid: null, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns an object with the correct structure', async () => { - const actual = await fn(context, { visConfig: JSON.stringify(visConfig) }); - expect(actual).toMatchSnapshot(); - }); - - it('calls response handler with correct values', async () => { - const { geohash, metric, geocentroid } = visConfig.dimensions; - await fn(context, { visConfig: JSON.stringify(visConfig) }); - expect(convertToGeoJson).toHaveBeenCalledTimes(1); - expect(convertToGeoJson).toHaveBeenCalledWith(context, { - geohash, - metric, - geocentroid, - }); - }); -}); diff --git a/src/plugins/tile_map/public/tile_map_fn.ts b/src/plugins/tile_map/public/tile_map_fn.ts deleted file mode 100644 index 53372b46c0bf8f..00000000000000 --- a/src/plugins/tile_map/public/tile_map_fn.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -import type { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; -import { TileMapVisConfig, TileMapVisData } from './types'; - -interface Arguments { - visConfig: string | null; -} - -export interface TileMapVisRenderValue { - visData: TileMapVisData; - visType: 'tile_map'; - visConfig: TileMapVisConfig; -} - -export type TileMapExpressionFunctionDefinition = ExpressionFunctionDefinition< - 'tilemap', - Datatable, - Arguments, - Promise> ->; - -export const createTileMapFn = (): TileMapExpressionFunctionDefinition => ({ - name: 'tilemap', - type: 'render', - context: { - types: ['datatable'], - }, - help: i18n.translate('tileMap.function.help', { - defaultMessage: 'Tilemap visualization', - }), - args: { - visConfig: { - types: ['string', 'null'], - default: '"{}"', - help: '', - }, - }, - async fn(context, args, handlers) { - const visConfig = args.visConfig && JSON.parse(args.visConfig); - const { geohash, metric, geocentroid } = visConfig.dimensions; - - const { convertToGeoJson } = await import('./utils'); - const convertedData = convertToGeoJson(context, { - geohash, - metric, - geocentroid, - }); - - if (handlers?.inspectorAdapters?.tables) { - handlers.inspectorAdapters.tables.logDatatable('default', context); - } - return { - type: 'render', - as: 'tile_map_vis', - value: { - visData: convertedData, - visType: 'tile_map', - visConfig, - }, - }; - }, -}); diff --git a/src/plugins/tile_map/public/tile_map_renderer.tsx b/src/plugins/tile_map/public/tile_map_renderer.tsx deleted file mode 100644 index 7339e4bf64b1c3..00000000000000 --- a/src/plugins/tile_map/public/tile_map_renderer.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { lazy } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; - -import { ExpressionRenderDefinition } from 'src/plugins/expressions'; -import { VisualizationContainer } from '../../visualizations/public'; -import { TileMapVisualizationDependencies } from './plugin'; -import { TileMapVisRenderValue } from './tile_map_fn'; - -const TileMapVisualization = lazy(() => import('./tile_map_visualization_component')); - -export const getTileMapRenderer: ( - deps: TileMapVisualizationDependencies -) => ExpressionRenderDefinition = (deps) => ({ - name: 'tile_map_vis', - reuseDomNode: true, - render: async (domNode, { visConfig, visData }, handlers) => { - handlers.onDestroy(() => { - unmountComponentAtNode(domNode); - }); - - render( - - - , - domNode - ); - }, -}); diff --git a/src/plugins/tile_map/public/tile_map_type.ts b/src/plugins/tile_map/public/tile_map_type.ts deleted file mode 100644 index 5e71351f1bd563..00000000000000 --- a/src/plugins/tile_map/public/tile_map_type.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { VisTypeDefinition } from 'src/plugins/visualizations/public'; - -// @ts-expect-error -import { supportsCssFilters } from './css_filters'; -import { TileMapOptionsLazy } from './components'; -import { getDeprecationMessage } from './get_deprecation_message'; -import { TileMapVisualizationDependencies } from './plugin'; -import { toExpressionAst } from './to_ast'; -import { TileMapVisParams } from './types'; -import { setTmsLayers } from './services'; - -export function createTileMapTypeDefinition( - dependencies: TileMapVisualizationDependencies -): VisTypeDefinition { - const { uiSettings, getServiceSettings } = dependencies; - - return { - name: 'tile_map', - getInfoMessage: getDeprecationMessage, - title: i18n.translate('tileMap.vis.mapTitle', { - defaultMessage: 'Coordinate Map', - }), - icon: 'visMapCoordinate', - description: i18n.translate('tileMap.vis.mapDescription', { - defaultMessage: 'Plot latitude and longitude coordinates on a map', - }), - visConfig: { - canDesaturate: Boolean(supportsCssFilters), - defaults: { - colorSchema: 'Yellow to Red', - mapType: 'Scaled Circle Markers', - isDesaturated: true, - addTooltip: true, - heatClusterSize: 1.5, - legendPosition: 'bottomright', - mapZoom: 2, - mapCenter: [0, 0], - wms: uiSettings.get('visualization:tileMap:WMSdefaults'), - }, - }, - toExpressionAst, - editorConfig: { - optionsTemplate: TileMapOptionsLazy, - schemas: [ - { - group: 'metrics', - name: 'metric', - title: i18n.translate('tileMap.vis.map.editorConfig.schemas.metricTitle', { - defaultMessage: 'Value', - }), - min: 1, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], - defaults: [{ schema: 'metric', type: 'count' }], - }, - { - group: 'buckets', - name: 'segment', - title: i18n.translate('tileMap.vis.map.editorConfig.schemas.geoCoordinatesTitle', { - defaultMessage: 'Geo coordinates', - }), - aggFilter: ['geohash_grid'], - min: 1, - max: 1, - }, - ], - }, - setup: async (vis) => { - let tmsLayers; - - try { - const serviceSettings = await getServiceSettings(); - tmsLayers = await serviceSettings.getTMSServices(); - } catch (e) { - return vis; - } - - setTmsLayers(tmsLayers); - if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) { - vis.params.wms.selectedTmsLayer = tmsLayers[0]; - } - return vis; - }, - requiresSearch: true, - }; -} diff --git a/src/plugins/tile_map/public/tile_map_visualization.js b/src/plugins/tile_map/public/tile_map_visualization.js deleted file mode 100644 index ebce2de51bbbc1..00000000000000 --- a/src/plugins/tile_map/public/tile_map_visualization.js +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { get, round } from 'lodash'; -import { getFormatService, getQueryService, getKibanaLegacy } from './services'; -import { mapTooltipProvider, lazyLoadMapsLegacyModules } from '../../maps_legacy/public'; -import { tooltipFormatter } from './tooltip_formatter'; -import { geoContains } from './utils'; - -function scaleBounds(bounds) { - const scale = 0.5; // scale bounds by 50% - - const topLeft = bounds.top_left; - const bottomRight = bounds.bottom_right; - let latDiff = round(Math.abs(topLeft.lat - bottomRight.lat), 5); - const lonDiff = round(Math.abs(bottomRight.lon - topLeft.lon), 5); - // map height can be zero when vis is first created - if (latDiff === 0) latDiff = lonDiff; - - const latDelta = latDiff * scale; - let topLeftLat = round(topLeft.lat, 5) + latDelta; - if (topLeftLat > 90) topLeftLat = 90; - let bottomRightLat = round(bottomRight.lat, 5) - latDelta; - if (bottomRightLat < -90) bottomRightLat = -90; - const lonDelta = lonDiff * scale; - let topLeftLon = round(topLeft.lon, 5) - lonDelta; - if (topLeftLon < -180) topLeftLon = -180; - let bottomRightLon = round(bottomRight.lon, 5) + lonDelta; - if (bottomRightLon > 180) bottomRightLon = 180; - - return { - top_left: { lat: topLeftLat, lon: topLeftLon }, - bottom_right: { lat: bottomRightLat, lon: bottomRightLon }, - }; -} - -export const createTileMapVisualization = (dependencies) => { - const { getZoomPrecision, getPrecision, BaseMapsVisualization } = dependencies; - - return class CoordinateMapsVisualization extends BaseMapsVisualization { - constructor(element, handlers, initialVisParams) { - super(element, handlers, initialVisParams); - - this._geohashLayer = null; - this._tooltipFormatter = mapTooltipProvider(element, tooltipFormatter); - } - - updateGeohashAgg = () => { - const geohashAgg = this._getGeoHashAgg(); - if (!geohashAgg) return; - const updateVarsObject = { - name: 'bounds', - data: {}, - }; - const bounds = this._kibanaMap.getBounds(); - const mapCollar = scaleBounds(bounds); - if (!geoContains(geohashAgg.sourceParams.params.boundingBox, mapCollar)) { - updateVarsObject.data.boundingBox = { - top_left: mapCollar.top_left, - bottom_right: mapCollar.bottom_right, - }; - } else { - updateVarsObject.data.boundingBox = geohashAgg.sourceParams.params.boundingBox; - } - // todo: autoPrecision should be vis parameter, not aggConfig one - const zoomPrecision = getZoomPrecision(); - updateVarsObject.data.precision = geohashAgg.sourceParams.params.autoPrecision - ? zoomPrecision[this.handlers.uiState.get('mapZoom')] - : getPrecision(geohashAgg.sourceParams.params.precision); - - this.handlers.event(updateVarsObject); - }; - - async render(esResponse, visParams) { - getKibanaLegacy().loadFontAwesome(); - await super.render(esResponse, visParams); - } - - async _makeKibanaMap() { - await super._makeKibanaMap(this._params); - - let previousPrecision = this._kibanaMap.getGeohashPrecision(); - let precisionChange = false; - - this.handlers.uiState.on('change', (prop) => { - if (prop === 'mapZoom' || prop === 'mapCenter') { - this.updateGeohashAgg(); - } - }); - - this._kibanaMap.on('zoomchange', () => { - precisionChange = previousPrecision !== this._kibanaMap.getGeohashPrecision(); - previousPrecision = this._kibanaMap.getGeohashPrecision(); - }); - this._kibanaMap.on('zoomend', () => { - const geohashAgg = this._getGeoHashAgg(); - if (!geohashAgg) { - return; - } - const isAutoPrecision = - typeof geohashAgg.sourceParams.params.autoPrecision === 'boolean' - ? geohashAgg.sourceParams.params.autoPrecision - : true; - if (!isAutoPrecision) { - return; - } - if (precisionChange) { - this.updateGeohashAgg(); - } else { - //when we filter queries by collar - this._updateData(this._geoJsonFeatureCollectionAndMeta); - } - }); - - this._kibanaMap.addDrawControl(); - this._kibanaMap.on('drawCreated:rectangle', (event) => { - const geohashAgg = this._getGeoHashAgg(); - this.addSpatialFilter(geohashAgg, 'geo_bounding_box', event.bounds); - }); - this._kibanaMap.on('drawCreated:polygon', (event) => { - const geohashAgg = this._getGeoHashAgg(); - this.addSpatialFilter(geohashAgg, 'geo_polygon', { points: event.points }); - }); - } - - async _updateData(geojsonFeatureCollectionAndMeta) { - // Only recreate geohash layer when there is new aggregation data - // Exception is Heatmap: which needs to be redrawn every zoom level because the clustering is based on meters per pixel - if ( - this._getMapsParams().mapType !== 'Heatmap' && - geojsonFeatureCollectionAndMeta === this._geoJsonFeatureCollectionAndMeta - ) { - return; - } - - if (this._geohashLayer) { - this._kibanaMap.removeLayer(this._geohashLayer); - this._geohashLayer = null; - } - - if (!geojsonFeatureCollectionAndMeta) { - this._geoJsonFeatureCollectionAndMeta = null; - this._kibanaMap.removeLayer(this._geohashLayer); - this._geohashLayer = null; - return; - } - - if ( - !this._geoJsonFeatureCollectionAndMeta || - !geojsonFeatureCollectionAndMeta.featureCollection.features.length - ) { - this._geoJsonFeatureCollectionAndMeta = geojsonFeatureCollectionAndMeta; - this.updateGeohashAgg(); - } - - this._geoJsonFeatureCollectionAndMeta = geojsonFeatureCollectionAndMeta; - this._recreateGeohashLayer(); - } - - async _recreateGeohashLayer() { - const { GeohashLayer } = await import('./geohash_layer'); - - if (this._geohashLayer) { - this._kibanaMap.removeLayer(this._geohashLayer); - this._geohashLayer = null; - } - const geohashOptions = this._getGeohashOptions(); - this._geohashLayer = new GeohashLayer( - this._geoJsonFeatureCollectionAndMeta.featureCollection, - this._geoJsonFeatureCollectionAndMeta.meta, - geohashOptions, - this._kibanaMap.getZoomLevel(), - this._kibanaMap, - (await lazyLoadMapsLegacyModules()).L - ); - this._kibanaMap.addLayer(this._geohashLayer); - } - - async _updateParams() { - await super._updateParams(); - - this._kibanaMap.setDesaturateBaseLayer(this._params.isDesaturated); - - //avoid recreating the leaflet layer when there are option-changes that do not effect the representation - //e.g. tooltip-visibility, legend position, basemap-desaturation, ... - const geohashOptions = this._getGeohashOptions(); - if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) { - if (this._geoJsonFeatureCollectionAndMeta) { - this._recreateGeohashLayer(); - } - this._updateData(this._geoJsonFeatureCollectionAndMeta); - } - } - - _getGeohashOptions() { - const newParams = this._getMapsParams(); - const metricDimension = this._params.dimensions.metric; - const metricLabel = metricDimension ? metricDimension.label : ''; - const metricFormat = getFormatService().deserialize( - metricDimension && metricDimension.format - ); - - return { - label: metricLabel, - valueFormatter: this._geoJsonFeatureCollectionAndMeta - ? metricFormat.getConverterFor('text') - : null, - tooltipFormatter: this._geoJsonFeatureCollectionAndMeta - ? this._tooltipFormatter.bind(null, metricLabel, metricFormat.getConverterFor('text')) - : null, - mapType: newParams.mapType, - isFilteredByCollar: this._isFilteredByCollar(), - colorRamp: newParams.colorSchema, - heatmap: { - heatClusterSize: newParams.heatClusterSize, - }, - }; - } - - addSpatialFilter(agg, filterName, filterData) { - if (!agg) { - return; - } - - const indexPatternName = agg.indexPatternId; - const field = agg.field; - const filter = { meta: { negate: false, index: indexPatternName } }; - filter[filterName] = { ignore_unmapped: true }; - filter[filterName][field] = filterData; - - const { filterManager } = getQueryService(); - filterManager.addFilters([filter]); - } - - _getGeoHashAgg() { - return ( - this._geoJsonFeatureCollectionAndMeta && this._geoJsonFeatureCollectionAndMeta.meta.geohash - ); - } - - _isFilteredByCollar() { - const DEFAULT = false; - const agg = this._getGeoHashAgg(); - if (agg) { - return get(agg, 'sourceParams.params.isFilteredByCollar', DEFAULT); - } else { - return DEFAULT; - } - } - }; -}; diff --git a/src/plugins/tile_map/public/tile_map_visualization.scss b/src/plugins/tile_map/public/tile_map_visualization.scss deleted file mode 100644 index 4298b06c763dab..00000000000000 --- a/src/plugins/tile_map/public/tile_map_visualization.scss +++ /dev/null @@ -1,4 +0,0 @@ -.tlmChart__wrapper, .tlmChart { - flex: 1 1 0; - display: flex; -} diff --git a/src/plugins/tile_map/public/tile_map_visualization_component.tsx b/src/plugins/tile_map/public/tile_map_visualization_component.tsx deleted file mode 100644 index 094efc63312e5e..00000000000000 --- a/src/plugins/tile_map/public/tile_map_visualization_component.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useEffect, useMemo, useRef } from 'react'; -import { EuiResizeObserver } from '@elastic/eui'; -import { throttle } from 'lodash'; - -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; -import { PersistedState } from 'src/plugins/visualizations/public'; -import { TileMapVisualizationDependencies } from './plugin'; -import { TileMapVisConfig, TileMapVisData } from './types'; -// @ts-expect-error -import { createTileMapVisualization } from './tile_map_visualization'; - -import './tile_map_visualization.scss'; - -interface TileMapVisController { - render(visData?: TileMapVisData, visConfig?: TileMapVisConfig): Promise; - resize(): void; - destroy(): void; -} - -interface TileMapVisualizationProps { - deps: TileMapVisualizationDependencies; - handlers: IInterpreterRenderHandlers; - visData: TileMapVisData; - visConfig: TileMapVisConfig; -} - -const TileMapVisualization = ({ - deps, - handlers, - visData, - visConfig, -}: TileMapVisualizationProps) => { - const chartDiv = useRef(null); - const visController = useRef(null); - const isFirstRender = useRef(true); - const uiState = handlers.uiState as PersistedState; - - useEffect(() => { - if (chartDiv.current && isFirstRender.current) { - isFirstRender.current = false; - const Controller = createTileMapVisualization(deps); - visController.current = new Controller(chartDiv.current, handlers, visConfig); - } - }, [deps, handlers, visConfig, visData]); - - useEffect(() => { - visController.current?.render(visData, visConfig).then(handlers.done); - }, [visData, visConfig, handlers.done]); - - useEffect(() => { - const onUiStateChange = () => { - visController.current?.render().then(handlers.done); - }; - - uiState.on('change', onUiStateChange); - - return () => { - uiState.off('change', onUiStateChange); - }; - }, [uiState, handlers.done]); - - useEffect(() => { - return () => { - visController.current?.destroy(); - visController.current = null; - }; - }, []); - - const updateChartSize = useMemo(() => throttle(() => visController.current?.resize(), 300), []); - - return ( - - {(resizeRef) => ( -
-
-
- )} - - ); -}; - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export { TileMapVisualization as default }; diff --git a/src/plugins/tile_map/public/to_ast.ts b/src/plugins/tile_map/public/to_ast.ts deleted file mode 100644 index b23a10909661b4..00000000000000 --- a/src/plugins/tile_map/public/to_ast.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - EsaggsExpressionFunctionDefinition, - IndexPatternLoadExpressionFunctionDefinition, -} from '../../data/public'; -import { buildExpression, buildExpressionFunction } from '../../expressions/public'; -import { getVisSchemas, VisToExpressionAst } from '../../visualizations/public'; -import { TileMapExpressionFunctionDefinition } from './tile_map_fn'; -import { TileMapVisConfig, TileMapVisParams } from './types'; - -export const toExpressionAst: VisToExpressionAst = (vis, params) => { - const esaggs = buildExpressionFunction('esaggs', { - index: buildExpression([ - buildExpressionFunction('indexPatternLoad', { - id: vis.data.indexPattern!.id!, - }), - ]), - metricsAtAllLevels: false, - partialRows: false, - aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())), - }); - - const schemas = getVisSchemas(vis, params); - - const visConfig: TileMapVisConfig = { - ...vis.params, - dimensions: { - metric: schemas.metric[0], - geohash: schemas.segment ? schemas.segment[0] : null, - geocentroid: schemas.geo_centroid ? schemas.geo_centroid[0] : null, - }, - }; - - const tilemap = buildExpressionFunction('tilemap', { - visConfig: JSON.stringify(visConfig), - }); - - const ast = buildExpression([esaggs, tilemap]); - - return ast.toAst(); -}; diff --git a/src/plugins/tile_map/public/tooltip_formatter.js b/src/plugins/tile_map/public/tooltip_formatter.js deleted file mode 100644 index 28a08d60b0be98..00000000000000 --- a/src/plugins/tile_map/public/tooltip_formatter.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -export function tooltipFormatter(metricTitle, metricFormat, feature) { - if (!feature) { - return ''; - } - - return [ - { - label: metricTitle, - value: metricFormat(feature.properties.value), - }, - { - label: i18n.translate('tileMap.tooltipFormatter.latitudeLabel', { - defaultMessage: 'Latitude', - }), - value: feature.geometry.coordinates[1], - }, - { - label: i18n.translate('tileMap.tooltipFormatter.longitudeLabel', { - defaultMessage: 'Longitude', - }), - value: feature.geometry.coordinates[0], - }, - ]; -} diff --git a/src/plugins/tile_map/public/types.ts b/src/plugins/tile_map/public/types.ts deleted file mode 100644 index 89aa27c36b6597..00000000000000 --- a/src/plugins/tile_map/public/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { FeatureCollection } from 'geojson'; -import type { SchemaConfig } from 'src/plugins/visualizations/public'; -import type { DatatableColumnMeta } from 'src/plugins/expressions'; -import type { WMSOptions } from 'src/plugins/maps_legacy/public'; -import type { MapTypes } from './utils/map_types'; - -export interface TileMapVisData { - featureCollection: FeatureCollection; - meta: { - min: number; - max: number; - geohash?: DatatableColumnMeta; - geohashPrecision: number | undefined; - geohashGridDimensionsAtEquator: [number, number] | undefined; - }; -} - -export interface TileMapVisDimensions { - metric: SchemaConfig; - geohash: SchemaConfig | null; - geocentroid: SchemaConfig | null; -} - -export interface TileMapVisParams { - colorSchema: string; - mapType: MapTypes; - isDesaturated: boolean; - addTooltip: boolean; - heatClusterSize: number; - legendPosition: 'bottomright' | 'bottomleft' | 'topright' | 'topleft'; - mapZoom: number; - mapCenter: [number, number]; - wms: WMSOptions; -} - -export interface TileMapVisConfig extends TileMapVisParams { - dimensions: TileMapVisDimensions; -} diff --git a/src/plugins/tile_map/public/utils/convert_to_geojson.ts b/src/plugins/tile_map/public/utils/convert_to_geojson.ts deleted file mode 100644 index 57ece8d5ccd479..00000000000000 --- a/src/plugins/tile_map/public/utils/convert_to_geojson.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Feature } from 'geojson'; -import type { Datatable } from '../../../expressions/public'; -import type { TileMapVisDimensions, TileMapVisData } from '../types'; -import { decodeGeoHash } from './decode_geo_hash'; -import { gridDimensions } from './grid_dimensions'; - -export function convertToGeoJson( - tabifiedResponse: Datatable, - { geohash, geocentroid, metric }: TileMapVisDimensions -): TileMapVisData { - let features: Feature[]; - let min = Infinity; - let max = -Infinity; - - if (tabifiedResponse && tabifiedResponse.rows) { - const table = tabifiedResponse; - const geohashColumn = geohash ? table.columns[geohash.accessor] : null; - - if (!geohashColumn) { - features = []; - } else { - const metricColumn = table.columns[metric.accessor]; - const geocentroidColumn = geocentroid ? table.columns[geocentroid.accessor] : null; - - features = table.rows - .map((row) => { - const geohashValue = row[geohashColumn.id]; - if (!geohashValue) return false; - const geohashLocation = decodeGeoHash(geohashValue); - - let pointCoordinates: number[]; - if (geocentroidColumn) { - const location = row[geocentroidColumn.id]; - pointCoordinates = [location.lon, location.lat]; - } else { - pointCoordinates = [geohashLocation.longitude[2], geohashLocation.latitude[2]]; - } - - const rectangle = [ - [geohashLocation.latitude[0], geohashLocation.longitude[0]], - [geohashLocation.latitude[0], geohashLocation.longitude[1]], - [geohashLocation.latitude[1], geohashLocation.longitude[1]], - [geohashLocation.latitude[1], geohashLocation.longitude[0]], - ]; - - const centerLatLng = [geohashLocation.latitude[2], geohashLocation.longitude[2]]; - - if (geohash?.params.useGeocentroid) { - // see https://github.com/elastic/elasticsearch/issues/24694 for why clampGrid is used - pointCoordinates[0] = clampGrid( - pointCoordinates[0], - geohashLocation.longitude[0], - geohashLocation.longitude[1] - ); - pointCoordinates[1] = clampGrid( - pointCoordinates[1], - geohashLocation.latitude[0], - geohashLocation.latitude[1] - ); - } - - const value = row[metricColumn.id]; - min = Math.min(min, value); - max = Math.max(max, value); - - return { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: pointCoordinates, - }, - properties: { - geohash: geohashValue, - geohash_meta: { - center: centerLatLng, - rectangle, - }, - value, - }, - } as Feature; - }) - .filter((row): row is Feature => !!row); - } - } else { - features = []; - } - - const convertedData: TileMapVisData = { - featureCollection: { - type: 'FeatureCollection', - features, - }, - meta: { - min, - max, - geohashPrecision: geohash?.params.precision, - geohashGridDimensionsAtEquator: geohash?.params.precision - ? gridDimensions(geohash.params.precision) - : undefined, - }, - }; - - if (geohash && geohash.accessor) { - convertedData.meta.geohash = tabifiedResponse.columns[geohash.accessor].meta; - } - - return convertedData; -} - -function clampGrid(val: number, min: number, max: number) { - if (val > max) val = max; - else if (val < min) val = min; - return val; -} diff --git a/src/plugins/tile_map/public/utils/decode_geo_hash.test.ts b/src/plugins/tile_map/public/utils/decode_geo_hash.test.ts deleted file mode 100644 index 8a62a6f7d6a396..00000000000000 --- a/src/plugins/tile_map/public/utils/decode_geo_hash.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { decodeGeoHash } from './decode_geo_hash'; - -test('decodeGeoHash', () => { - expect(decodeGeoHash('drm3btev3e86')).toEqual({ - latitude: [41.119999922811985, 41.12000009045005, 41.12000000663102], - longitude: [-71.34000029414892, -71.3399999588728, -71.34000012651086], - }); -}); diff --git a/src/plugins/tile_map/public/utils/decode_geo_hash.ts b/src/plugins/tile_map/public/utils/decode_geo_hash.ts deleted file mode 100644 index bbabfd007d5c87..00000000000000 --- a/src/plugins/tile_map/public/utils/decode_geo_hash.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -interface DecodedGeoHash { - latitude: number[]; - longitude: number[]; -} - -/** - * Decodes geohash to object containing - * top-left and bottom-right corners of - * rectangle and center point. - */ -export function decodeGeoHash(geohash: string): DecodedGeoHash { - const BITS: number[] = [16, 8, 4, 2, 1]; - const BASE32: string = '0123456789bcdefghjkmnpqrstuvwxyz'; - let isEven: boolean = true; - const lat: number[] = []; - const lon: number[] = []; - lat[0] = -90.0; - lat[1] = 90.0; - lon[0] = -180.0; - lon[1] = 180.0; - let latErr: number = 90.0; - let lonErr: number = 180.0; - [...geohash].forEach((nextChar: string) => { - const cd: number = BASE32.indexOf(nextChar); - for (let j = 0; j < 5; j++) { - const mask: number = BITS[j]; - if (isEven) { - lonErr = lonErr /= 2; - refineInterval(lon, cd, mask); - } else { - latErr = latErr /= 2; - refineInterval(lat, cd, mask); - } - isEven = !isEven; - } - }); - lat[2] = (lat[0] + lat[1]) / 2; - lon[2] = (lon[0] + lon[1]) / 2; - - return { - latitude: lat, - longitude: lon, - }; -} - -function refineInterval(interval: number[], cd: number, mask: number) { - if (cd & mask) { /* eslint-disable-line */ - interval[0] = (interval[0] + interval[1]) / 2; - } else { - interval[1] = (interval[0] + interval[1]) / 2; - } -} - -interface GeoBoundingBoxCoordinate { - lat: number; - lon: number; -} - -interface GeoBoundingBox { - top_left: GeoBoundingBoxCoordinate; - bottom_right: GeoBoundingBoxCoordinate; -} - -export function geoContains(collar?: GeoBoundingBox, bounds?: GeoBoundingBox) { - if (!bounds || !collar) return false; - // test if bounds top_left is outside collar - if (bounds.top_left.lat > collar.top_left.lat || bounds.top_left.lon < collar.top_left.lon) { - return false; - } - - // test if bounds bottom_right is outside collar - if ( - bounds.bottom_right.lat < collar.bottom_right.lat || - bounds.bottom_right.lon > collar.bottom_right.lon - ) { - return false; - } - - // both corners are inside collar so collar contains bounds - return true; -} diff --git a/src/plugins/tile_map/public/utils/grid_dimensions.ts b/src/plugins/tile_map/public/utils/grid_dimensions.ts deleted file mode 100644 index 346d42227cd240..00000000000000 --- a/src/plugins/tile_map/public/utils/grid_dimensions.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// geohash precision mapping of geohash grid cell dimensions (width x height, in meters) at equator. -// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator -const gridAtEquator: { [key: number]: [number, number] } = { - 1: [5009400, 4992600], - 2: [1252300, 624100], - 3: [156500, 156000], - 4: [39100, 19500], - 5: [4900, 4900], - 6: [1200, 609.4], - 7: [152.9, 152.4], - 8: [38.2, 19], - 9: [4.8, 4.8], - 10: [1.2, 0.595], - 11: [0.149, 0.149], - 12: [0.037, 0.019], -}; - -export function gridDimensions(precision: number) { - return gridAtEquator[precision]; -} diff --git a/src/plugins/tile_map/public/utils/index.ts b/src/plugins/tile_map/public/utils/index.ts deleted file mode 100644 index a89f1e9d537642..00000000000000 --- a/src/plugins/tile_map/public/utils/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { convertToGeoJson } from './convert_to_geojson'; -export { geoContains } from './decode_geo_hash'; diff --git a/src/plugins/tile_map/public/utils/map_types.ts b/src/plugins/tile_map/public/utils/map_types.ts deleted file mode 100644 index afcc8e02ac963a..00000000000000 --- a/src/plugins/tile_map/public/utils/map_types.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export enum MapTypes { - ScaledCircleMarkers = 'Scaled Circle Markers', - ShadedCircleMarkers = 'Shaded Circle Markers', - ShadedGeohashGrid = 'Shaded Geohash Grid', - Heatmap = 'Heatmap', -} diff --git a/src/plugins/tile_map/server/index.ts b/src/plugins/tile_map/server/index.ts deleted file mode 100644 index a7fa77a67d9099..00000000000000 --- a/src/plugins/tile_map/server/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const plugin = () => ({ - setup() {}, - start() {}, -}); diff --git a/src/plugins/tile_map/tsconfig.json b/src/plugins/tile_map/tsconfig.json deleted file mode 100644 index fec191402f2ab3..00000000000000 --- a/src/plugins/tile_map/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./target/types", - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": ["public/**/*", "server/**/*"], - "references": [ - { "path": "../maps_legacy/tsconfig.json" }, - { "path": "../maps_ems/tsconfig.json" }, - { "path": "../vis_default_editor/tsconfig.json" }, - ] -} diff --git a/src/plugins/vis_type_vega/public/data_model/vega_parser.ts b/src/plugins/vis_type_vega/public/data_model/vega_parser.ts index 95b3b573a6bfaa..bc7d72c0428419 100644 --- a/src/plugins/vis_type_vega/public/data_model/vega_parser.ts +++ b/src/plugins/vis_type_vega/public/data_model/vega_parser.ts @@ -273,10 +273,8 @@ The URL is an identifier only. Kibana and your browser will never access this UR vegaLogger.warn = this._onWarning.bind(this); this.spec = compile(this.vlspec as TopLevelSpec, { logger: vegaLogger }).spec; - // When using VL with the type=map and user did not provid their own projection settings, + // When using Vega-Lite (VL) with the type=map and user did not provid their own projection settings, // remove the default projection that was generated by VegaLite compiler. - // This way we let leaflet-vega library inject a different default projection for tile maps. - // Also, VL injects default padding and autosize values, but neither should be set for vega-leaflet. if (this.useMap) { if (!this.spec || !this.vlspec) return; const hasConfig = _.isPlainObject(this.vlspec.config); diff --git a/test/functional/apps/dashboard/dashboard_state.ts b/test/functional/apps/dashboard/dashboard_state.ts index ea2031f370eba4..1a9cf3b7593a03 100644 --- a/test/functional/apps/dashboard/dashboard_state.ts +++ b/test/functional/apps/dashboard/dashboard_state.ts @@ -18,7 +18,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'visualize', 'header', 'discover', - 'tileMap', 'visChart', 'share', 'timePicker', @@ -27,11 +26,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const queryBar = getService('queryBar'); const pieChart = getService('pieChart'); - const inspector = getService('inspector'); const retry = getService('retry'); const elasticChart = getService('elasticChart'); const kibanaServer = getService('kibanaServer'); - const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardAddPanel = getService('dashboardAddPanel'); const enableNewChartLibraryDebug = async () => { @@ -166,38 +163,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(headers.length).to.be(0); }); - it('Tile map with no changes will update with visualization changes', async () => { - await PageObjects.dashboard.gotoDashboardLandingPage(); - - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.timePicker.setHistoricalDataRange(); - - await dashboardAddPanel.addVisualization('Visualization TileMap'); - await PageObjects.dashboard.saveDashboard('No local edits'); - - await dashboardPanelActions.openInspector(); - const tileMapData = await inspector.getTableData(); - await inspector.close(); - - await PageObjects.dashboard.switchToEditMode(); - await dashboardPanelActions.openContextMenu(); - await dashboardPanelActions.clickEdit(); - - await PageObjects.tileMap.clickMapZoomIn(); - await PageObjects.tileMap.clickMapZoomIn(); - await PageObjects.tileMap.clickMapZoomIn(); - await PageObjects.tileMap.clickMapZoomIn(); - - await PageObjects.visualize.saveVisualizationExpectSuccess('Visualization TileMap'); - - await PageObjects.header.clickDashboard(); - - await dashboardPanelActions.openInspector(); - const changedTileMapData = await inspector.getTableData(); - await inspector.close(); - expect(changedTileMapData.length).to.not.equal(tileMapData.length); - }); - const getUrlFromShare = async () => { await PageObjects.share.clickShareTopNavButton(); const sharedUrl = await PageObjects.share.getSharedUrl(); diff --git a/test/functional/apps/dashboard/embeddable_library.ts b/test/functional/apps/dashboard/embeddable_library.ts index d66f6e834c367f..fd1aa0d91def70 100644 --- a/test/functional/apps/dashboard/embeddable_library.ts +++ b/test/functional/apps/dashboard/embeddable_library.ts @@ -68,44 +68,5 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(libraryActionExists).to.be(true); }); - - it('unlink map panel from embeddable library', async () => { - // add map panel from library - await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.filterEmbeddableNames('Rendering Test: geo map'); - await find.clickByButtonText('Rendering Test: geo map'); - await dashboardAddPanel.closeAddPanel(); - - const originalPanel = await testSubjects.find('embeddablePanelHeading-RenderingTest:geomap'); - await panelActions.unlinkFromLibary(originalPanel); - await testSubjects.existOrFail('unlinkPanelSuccess'); - - const updatedPanel = await testSubjects.find('embeddablePanelHeading-RenderingTest:geomap'); - const libraryActionExists = await testSubjects.descendantExists( - 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', - updatedPanel - ); - expect(libraryActionExists).to.be(false); - - await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.filterEmbeddableNames('Rendering Test: geo map'); - await find.existsByLinkText('Rendering Test: geo map'); - await dashboardAddPanel.closeAddPanel(); - }); - - it('save map panel to embeddable library', async () => { - const originalPanel = await testSubjects.find('embeddablePanelHeading-RenderingTest:geomap'); - await panelActions.saveToLibrary('Rendering Test: geo map - copy', originalPanel); - await testSubjects.existOrFail('addPanelToLibrarySuccess'); - - const updatedPanel = await testSubjects.find( - 'embeddablePanelHeading-RenderingTest:geomap-copy' - ); - const libraryActionExists = await testSubjects.descendantExists( - 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', - updatedPanel - ); - expect(libraryActionExists).to.be(true); - }); }); } diff --git a/test/functional/apps/visualize/_region_map.ts b/test/functional/apps/visualize/_region_map.ts deleted file mode 100644 index 916e8dbaee3a0d..00000000000000 --- a/test/functional/apps/visualize/_region_map.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; - -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - describe('vector map', function () { - const inspector = getService('inspector'); - const log = getService('log'); - const find = getService('find'); - const PageObjects = getPageObjects(['visualize', 'visEditor', 'timePicker']); - - before(async function () { - await PageObjects.visualize.initTests(); - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewAggBasedVisualization(); - log.debug('clickRegionMap'); - await PageObjects.visualize.clickRegionMap(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - log.debug('Bucket = Shape field'); - await PageObjects.visEditor.clickBucket('Shape field'); - log.debug('Aggregation = Terms'); - await PageObjects.visEditor.selectAggregation('Terms'); - log.debug('Field = geo.src'); - await PageObjects.visEditor.selectField('geo.src'); - await PageObjects.visEditor.clickGo(); - }); - - describe('vector map', function indexPatternCreation() { - it('should have inspector enabled', async function () { - await inspector.expectIsEnabled(); - }); - - it('should show results after clicking play (join on states)', async function () { - const expectedData = [ - ['CN', '2,592'], - ['IN', '2,373'], - ['US', '1,194'], - ['ID', '489'], - ['BR', '415'], - ]; - await inspector.open(); - await inspector.expectTableData(expectedData); - await inspector.close(); - }); - - it('should change results after changing layer to world', async function () { - await PageObjects.visEditor.clickOptionsTab(); - await PageObjects.visEditor.setSelectByOptionText( - 'regionMapOptionsSelectLayer', - 'World Countries' - ); - - // ensure all fields are there - await PageObjects.visEditor.setSelectByOptionText( - 'regionMapOptionsSelectJoinField', - 'ISO 3166-1 alpha-2 code' - ); - await PageObjects.visEditor.setSelectByOptionText( - 'regionMapOptionsSelectJoinField', - 'ISO 3166-1 alpha-3 code' - ); - await PageObjects.visEditor.setSelectByOptionText( - 'regionMapOptionsSelectJoinField', - 'name' - ); - await PageObjects.visEditor.setSelectByOptionText( - 'regionMapOptionsSelectJoinField', - 'ISO 3166-1 alpha-2 code' - ); - - await inspector.open(); - const actualData = await inspector.getTableData(); - const expectedData = [ - ['CN', '2,592'], - ['IN', '2,373'], - ['US', '1,194'], - ['ID', '489'], - ['BR', '415'], - ]; - expect(actualData).to.eql(expectedData); - - await inspector.close(); - }); - - it('should contain a dropdown with the default road_map base layer as an option', async () => { - const selectField = await find.byCssSelector('#wmsOptionsSelectTmsLayer'); - const $ = await selectField.parseDomContent(); - const optionsText = $('option') - .toArray() - .map((option) => $(option).text()); - - expect(optionsText.includes('road_map')).to.be(true); - }); - }); - }); -} diff --git a/test/functional/apps/visualize/_tile_map.ts b/test/functional/apps/visualize/_tile_map.ts deleted file mode 100644 index 812b6a7d868021..00000000000000 --- a/test/functional/apps/visualize/_tile_map.ts +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; - -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const retry = getService('retry'); - const inspector = getService('inspector'); - const filterBar = getService('filterBar'); - const browser = getService('browser'); - const PageObjects = getPageObjects([ - 'common', - 'visualize', - 'visEditor', - 'visChart', - 'timePicker', - 'tileMap', - ]); - - describe('tile map visualize app', function () { - describe('incomplete config', function describeIndexTests() { - before(async function () { - await PageObjects.visualize.initTests(); - await browser.setWindowSize(1280, 1000); - - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewAggBasedVisualization(); - log.debug('clickTileMap'); - await PageObjects.visualize.clickTileMap(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - // do not configure aggs - }); - - it('should be able to zoom in twice', async () => { - // should not throw - await PageObjects.tileMap.clickMapZoomIn(); - await PageObjects.tileMap.clickMapZoomIn(); - }); - }); - - describe('complete config', function describeIndexTests() { - before(async function () { - await browser.setWindowSize(1280, 1000); - - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewAggBasedVisualization(); - log.debug('clickTileMap'); - await PageObjects.visualize.clickTileMap(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - log.debug('select bucket Geo Coordinates'); - await PageObjects.visEditor.clickBucket('Geo coordinates'); - log.debug('Click aggregation Geohash'); - await PageObjects.visEditor.selectAggregation('Geohash'); - log.debug('Click field geo.coordinates'); - await retry.try(async function tryingForTime() { - await PageObjects.visEditor.selectField('geo.coordinates'); - }); - await PageObjects.visEditor.clickGo(); - }); - - type SampleTableData = Array; - - /** - * manually compare data due to possible small difference in numbers. This is browser dependent. - */ - function compareTableData(actual: string[][], expected: SampleTableData[]) { - log.debug('comparing expected: ', expected); - log.debug('with actual: ', actual); - - const roundedValues = actual.map((row) => { - // Parse last element in each row as JSON and floor the lat/long value - const coords = JSON.parse(row[row.length - 1]); - return [ - ...row.slice(0, -1), - { - lat: Math.floor(parseFloat(coords.lat)), - lon: Math.floor(parseFloat(coords.lon)), - }, - ]; - }); - - expect(roundedValues).to.eql(expected); - } - - describe('tile map chart', function indexPatternCreation() { - it('should have inspector enabled', async function () { - await inspector.expectIsEnabled(); - }); - - it('should show correct tile map data on default zoom level', async function () { - const expectedTableData = [ - ['-', '9', '5,787', { lat: 37, lon: -104 }], - ['-', 'd', '5,600', { lat: 37, lon: -82 }], - ['-', 'c', '1,319', { lat: 47, lon: -110 }], - ['-', 'b', '999', { lat: 62, lon: -156 }], - ['-', 'f', '187', { lat: 45, lon: -83 }], - ['-', '8', '108', { lat: 18, lon: -157 }], - ]; - // level 1 - await PageObjects.tileMap.clickMapZoomOut(); - // level 0 - await PageObjects.tileMap.clickMapZoomOut(); - - await inspector.open(); - await inspector.setTablePageSize(50); - const actualTableData = await inspector.getTableData(); - await inspector.close(); - compareTableData(actualTableData, expectedTableData); - }); - - it('should not be able to zoom out beyond 0', async function () { - await PageObjects.tileMap.zoomAllTheWayOut(); - const enabled = await PageObjects.tileMap.getMapZoomOutEnabled(); - expect(enabled).to.be(false); - }); - - it('Fit data bounds should zoom to level 3', async function () { - const expectedPrecision2DataTable = [ - ['-', 'dn', '1,429', { lat: 36, lon: -85 }], - ['-', 'dp', '1,418', { lat: 41, lon: -85 }], - ['-', '9y', '1,215', { lat: 36, lon: -96 }], - ['-', '9z', '1,099', { lat: 42, lon: -96 }], - ['-', 'dr', '1,076', { lat: 42, lon: -74 }], - ['-', 'dj', '982', { lat: 31, lon: -85 }], - ['-', '9v', '938', { lat: 31, lon: -96 }], - ['-', '9q', '722', { lat: 36, lon: -120 }], - ['-', '9w', '475', { lat: 36, lon: -107 }], - ['-', 'cb', '457', { lat: 46, lon: -96 }], - ['-', 'c2', '453', { lat: 47, lon: -120 }], - ['-', '9x', '420', { lat: 41, lon: -107 }], - ['-', 'dq', '399', { lat: 37, lon: -78 }], - ['-', '9r', '396', { lat: 41, lon: -120 }], - ['-', '9t', '274', { lat: 32, lon: -107 }], - ['-', 'c8', '271', { lat: 47, lon: -107 }], - ['-', 'dh', '214', { lat: 26, lon: -82 }], - ['-', 'b6', '207', { lat: 60, lon: -162 }], - ['-', 'bd', '206', { lat: 59, lon: -153 }], - ['-', 'b7', '167', { lat: 64, lon: -163 }], - ]; - - await PageObjects.tileMap.clickMapFitDataBounds(); - await inspector.open(); - const data = await inspector.getTableData(); - await inspector.close(); - compareTableData(data, expectedPrecision2DataTable); - }); - - it('Fit data bounds works with pinned filter data', async () => { - const expectedPrecision2DataTable = [ - ['-', 'f05', '1', { lat: 45, lon: -85 }], - ['-', 'dpr', '1', { lat: 40, lon: -79 }], - ['-', '9qh', '1', { lat: 33, lon: -118 }], - ]; - - await filterBar.addFilter('bytes', 'is between', '19980', '19990'); - await filterBar.toggleFilterPinned('bytes'); - await PageObjects.tileMap.zoomAllTheWayOut(); - await PageObjects.tileMap.clickMapFitDataBounds(); - - await inspector.open(); - const data = await inspector.getTableData(); - await inspector.close(); - - await filterBar.removeAllFilters(); - compareTableData(data, expectedPrecision2DataTable); - }); - - it('Newly saved visualization retains map bounds', async () => { - const vizName1 = 'Visualization TileMap'; - - await PageObjects.tileMap.clickMapZoomIn(); - await PageObjects.tileMap.clickMapZoomIn(); - - const mapBounds = await PageObjects.tileMap.getMapBounds(); - await inspector.close(); - - await PageObjects.visualize.saveVisualizationExpectSuccess(vizName1); - - const afterSaveMapBounds = await PageObjects.tileMap.getMapBounds(); - - await inspector.close(); - // For some reason the values are slightly different, so we can't check that they are equal. But we did - // have a bug where after the save, there were _no_ map bounds. So this checks for the later case, but - // until we figure out how to make sure the map center is always the exact same, we can't comparison check. - expect(mapBounds).to.not.be(undefined); - expect(afterSaveMapBounds).to.not.be(undefined); - }); - }); - - describe('Only request data around extent of map option', () => { - it('when checked adds filters to aggregation', async () => { - const vizName1 = 'Visualization TileMap'; - await PageObjects.visualize.loadSavedVisualization(vizName1); - await inspector.open(); - await inspector.expectTableHeaders(['Filter', 'Geohash', 'Count', 'Geo Centroid']); - await inspector.close(); - }); - - it('when not checked does not add filters to aggregation', async () => { - await PageObjects.visEditor.toggleOpenEditor(2); - await PageObjects.visEditor.setIsFilteredByCollarCheckbox(false); - await PageObjects.visEditor.clickGo(); - await inspector.open(); - await inspector.expectTableHeaders(['Geohash', 'Count', 'Geo Centroid']); - await inspector.close(); - }); - - after(async () => { - await PageObjects.visEditor.setIsFilteredByCollarCheckbox(true); - await PageObjects.visEditor.clickGo(); - }); - }); - }); - }); -} diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 4af871bd9347dd..9004ecaf22d805 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -91,8 +91,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_linked_saved_searches')); loadTestFile(require.resolve('./_visualize_listing')); loadTestFile(require.resolve('./_add_to_dashboard.ts')); - loadTestFile(require.resolve('./_tile_map')); - loadTestFile(require.resolve('./_region_map')); }); describe('visualize ciGroup12', function () { diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index ea727069c927d5..210c8f61b23915 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -536,7 +536,6 @@ export class DashboardPageObject extends FtrService { { name: AREA_CHART_VIS_NAME, description: 'AreaChart' }, { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, { name: LINE_CHART_VIS_NAME, description: 'LineChart' }, - { name: 'Visualization TileMap', description: 'TileMap' }, { name: 'Visualization MetricChart', description: 'MetricChart' }, ]; } diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index 4c9cb150eca03a..cda2c7de44d3bb 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -24,7 +24,6 @@ import { VisualBuilderPageObject } from './visual_builder_page'; import { VisualizePageObject } from './visualize_page'; import { VisualizeEditorPageObject } from './visualize_editor_page'; import { VisualizeChartPageObject } from './visualize_chart_page'; -import { TileMapPageObject } from './tile_map_page'; import { TimeToVisualizePageObject } from './time_to_visualize_page'; import { TagCloudPageObject } from './tag_cloud_page'; import { VegaChartPageObject } from './vega_chart_page'; @@ -52,7 +51,6 @@ export const pageObjects = { visualize: VisualizePageObject, visEditor: VisualizeEditorPageObject, visChart: VisualizeChartPageObject, - tileMap: TileMapPageObject, timeToVisualize: TimeToVisualizePageObject, tagCloud: TagCloudPageObject, vegaChart: VegaChartPageObject, diff --git a/test/functional/page_objects/tile_map_page.ts b/test/functional/page_objects/tile_map_page.ts deleted file mode 100644 index 079ca919543e22..00000000000000 --- a/test/functional/page_objects/tile_map_page.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { FtrService } from '../ftr_provider_context'; - -export class TileMapPageObject extends FtrService { - private readonly find = this.ctx.getService('find'); - private readonly testSubjects = this.ctx.getService('testSubjects'); - private readonly retry = this.ctx.getService('retry'); - private readonly log = this.ctx.getService('log'); - private readonly inspector = this.ctx.getService('inspector'); - private readonly monacoEditor = this.ctx.getService('monacoEditor'); - private readonly header = this.ctx.getPageObject('header'); - - public async getZoomSelectors(zoomSelector: string) { - return await this.find.allByCssSelector(zoomSelector); - } - - public async clickMapButton(zoomSelector: string, waitForLoading?: boolean) { - await this.retry.try(async () => { - const zooms = await this.getZoomSelectors(zoomSelector); - for (let i = 0; i < zooms.length; i++) { - await zooms[i].click(); - } - if (waitForLoading) { - await this.header.waitUntilLoadingHasFinished(); - } - }); - } - - public async getVisualizationRequest() { - this.log.debug('getVisualizationRequest'); - await this.inspector.open(); - await this.testSubjects.click('inspectorViewChooser'); - await this.testSubjects.click('inspectorViewChooserRequests'); - await this.testSubjects.click('inspectorRequestDetailRequest'); - await this.find.byCssSelector('.react-monaco-editor-container'); - - return await this.monacoEditor.getCodeEditorValue(1); - } - - public async getMapBounds(): Promise { - const request = await this.getVisualizationRequest(); - const requestObject = JSON.parse(request); - - return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates']; - } - - public async clickMapZoomIn(waitForLoading = true) { - await this.clickMapButton('a.leaflet-control-zoom-in', waitForLoading); - } - - public async clickMapZoomOut(waitForLoading = true) { - await this.clickMapButton('a.leaflet-control-zoom-out', waitForLoading); - } - - public async getMapZoomEnabled(zoomSelector: string): Promise { - const zooms = await this.getZoomSelectors(zoomSelector); - const classAttributes = await Promise.all( - zooms.map(async (zoom) => await zoom.getAttribute('class')) - ); - return !classAttributes.join('').includes('leaflet-disabled'); - } - - public async zoomAllTheWayOut(): Promise { - // we can tell we're at level 1 because zoom out is disabled - return await this.retry.try(async () => { - await this.clickMapZoomOut(); - const enabled = await this.getMapZoomOutEnabled(); - // should be able to zoom more as current config has 0 as min level. - if (enabled) { - throw new Error('Not fully zoomed out yet'); - } - }); - } - - public async getMapZoomInEnabled() { - return await this.getMapZoomEnabled('a.leaflet-control-zoom-in'); - } - - public async getMapZoomOutEnabled() { - return await this.getMapZoomEnabled('a.leaflet-control-zoom-out'); - } - - public async clickMapFitDataBounds() { - return await this.clickMapButton('a.fa-crop'); - } -} diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 966a9d29b32644..1271fe5108f56d 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -165,14 +165,6 @@ export class VisualizePageObject extends FtrService { await this.clickVisType('line'); } - public async clickRegionMap() { - await this.clickVisType('region_map'); - } - - public async hasRegionMap() { - return await this.hasVisType('region_map'); - } - public async clickMarkdownWidget() { await this.clickVisType('markdown'); } @@ -189,14 +181,6 @@ export class VisualizePageObject extends FtrService { await this.clickVisType('pie'); } - public async clickTileMap() { - await this.clickVisType('tile_map'); - } - - public async hasTileMap() { - return await this.hasVisType('tile_map'); - } - public async clickTimelion() { await this.clickVisType('timelion'); } diff --git a/test/interpreter_functional/screenshots/baseline/partial_test_3.png b/test/interpreter_functional/screenshots/baseline/partial_test_3.png deleted file mode 100644 index b0edb637e0047abede245dead9edde3230cef14d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10750 zcmeHtc~p~Ex3|5%mD|@MtFKiHDAW-Z6a_INL)uaVWKu97AfS~HnPm(iKpa|l6%hdm zA_S@+DPt;#k$^;tA_Pc~DNG?CLl_h05JHlB&Vzlw`>yY2oT^c>L0Aiym%glh4sz$C)z~4ML7W{8G*pGlYKJCV#puN4yxdr9-h9?6~gA z->~r4&$sS>0)AcF=K%Fs_s~)m{<^a3zkX=lU$@!b_zO5?+4<>UoVvFa3;y%X<^${D zZ`-`j;jd%Kdhl1v_kZT0j}@w@!#6`8XSY@7{JkxY_TOHZ>fwcbO%x-PS^OqtI6=qCnqgJq)L2BTdB~td^c(!GW!GCo!p9bQq+$2--XE;iFm9P?}9%OvNO{G`Qf;3cw>?iC@NgEr@3~CP6%1$ZO zh*@}QyfSvVa%Ed;xqM}E#M!ZQ6Wk^&T~E8AfST9{r|l&OE16G9UhalZTHIO1e?jr1 z{bGqSQQ0rry3G9vZJqsy55_iOoo)=-8ShOfpRKz&?{t{43}Ccw-K)8SqBdgS#{UE!vr^o2u;B4NLz1m&( zcB*4Li{CklyE#c#&iXhTdpDv5d^*5$@cy(ns*8-AFzrdxUn82{dp4Gx5AP6v3MY`A zGtw@~7CW1b!rMe71a9X&fU2IhGQw6WI9P)IXhII+->`Ua@Fvx^@?&+;v-7H<%a_*7 z){>HTW%SN?<=BVDkoSAjac7H4S8Dner2N0Y4H%BrPGv{qHOb&-D;zlg#h-!N?;zdF zn|kfSK>*)hp{i|mO{7w8D zx62Y~$Tkc&Yp2JVK{W>ND3PZbQ487KTgC*j`E*?Xwj#}C74 zudfco2REELiYKNQWAK=)mx|LGg%h7rAaIQM)ZuVGD;K+&+snSQHSn zpkLQ~dd7QihT-KXa@_;nSkud!&^mBt%MRDFzcptaHQDAK?Q)ngJg4IeWOn1v8Gb9>_ zF#mOY{Fj>j^TTI(nH|0rO&R)H8@KAz-#+uD`O4yCx?CnjJl1b#&3NxNZmZiXd;Iqono&yj(YA211^nVc9kB+1(g5a#5tI`N6 z<5N>jZ{~FyG^#{ua4-^epO7^6anLzwI}(B->GugL|JNX@V%TKh+D+4(^(tSbK`j}- zt7+vh+@^GAO~rU}wUV)HL0lKI8|ok)Uw;aTb__Fcw}Ui7&(#o`&bqX##D>|G zRz{o#0Tsz0V|E|0@@&{-Ec4y=G&uF6=E%MHbE)@#3ay_`gRN^4P*wB80nI& z3)!W8RWh#+cyqO6G!w3)P$&nWa4*6$M0o#RAZcw?VPV-sVUc%9MJFp|LC7-#>Cc`w z)m`rRrTN;vE>1`|8*o1ZwK?sM!2pRxr3~LrSHF_#&FcQYe*2GM3l*$9fIEP7#0}9ls`FXj#6vc?FaX1`E<)_Us7y^|A zPxuWA*eJpE#nnqIOVgPn@4LG*0MkDP7GlpLx1ZsH!T5kQ7>;%ECWHZNAX0%zSVkWw zSYa^Zh2`b?$dSY1!G6_<=vP(06c&ON-UMJgphhbOgK=^FKZk{i;DKadJaA%V3}%F~ zRvPer1`aDk_7OT^Fyyt|dHxd*QcNt_0)qjj2D7m)YmqS!Oqsa-*b;bs?g!|YG z#+otu3)YxtBJ}d@$MrWp-B?zf9r@uFz)0NV{SMAwntz-!bNCK`#vR9HA?_i;M_8phh>(*3uAASKP{Bed$d)8x9#NhV~-ey}e2;E@iEyk=kk zyEPDk$&dH9wzj7Hv~k-gjU@BidicTv1ce|6I|5}`Wj1HO1l5yC)B-pTI3|>&(+F(= z);@W^hRJPP*0e|FyO2Suws8l3RRgQtUC3ZLShfH5niT+b1LrQgvPQ(MfyIHDCg0~; zz^%)4)gbdT0iHzwr(RZFF?V5L?Og4AMeg-XK7J%Io5v`!Lde=fgF!*!L z;3C3g3Eg9zd(@oaWv9X3b7ND*bEChZ%1?8$3;g^<^`^TmyrvFlKZcNOdmy4Wyd$iF3WThfv7tsk{_qa#eWa$ z0BadQo`5$~ryk_Rm#H*W6LhJ#i$}op4n%_hm?8!|I}>zk?k7v{vQ;>% zfu_%gmL%`c9u0v+VLD$SUbqk`?;f`!ii}R8UwZv;obTUeyxdYN`E46Bp@P?{vpgbdusVR;=wLOL+b8xx8bmqYMSB%hAr#85mP!pxI0K9yS5Y7eUyM zk6qGtar%7--}uLB1TrgyOueQ1n_Dr!|78qG{-mRLD3s07=QK4e>(`{ladw29QG-Dg zK5OeZShUcj8CYYmacgxl9^@Qaa~3YA@zy&{%JrKYyabepCZphXHz)R_GWz8d>(2J477PYxedzlx?3C2K*UAU^DtxHUzCO#4Gg436k?Cl! zo({sYcW`#hQswrTDJuyp*+L)@cv{x(PGYmpdZG_2k5c)QF-gG)SkFa%OB!2EH$C7MGo7red@U)UdceaEMF$RJT2KkecqE zk-U4!(iwCQ2&WQF_wY}wyn$&yuy6S@f^1P@C|phrg)T@URi`jaW1^_`IsGo!wCn?{ zT`?;Qb~&am9mOCxm^Tw8BhZkBvZ^6{V2)#1OB8)QX#a*c3FXID7RD(bSC6+*@lgw7 z$)zyC-!)yzyQ3Ckc~~~iX}#Lf!S!w*GlM>F18IXOtVE;w!$O7Cli3%p4%*LiQ1T3} zN70kPDNXyPvGpp=9MWu~P6G%XZ8Ob7c5LdYm|gDfVY4Hu1}pcXhsHKTDU9GDEiZPK zAY_<(w;A>b;qxA3iUZQ?&I~UYUjR7ExXZ zgB$Sdp{gus%+islf$^|=xJVO-~``rX+AA*)@Oz5a8RWKHm zecz|8)S9PDl^>+bW|y*vODZ8999r}J0;Zj)|D^*L1j9z!i-Q$hPtH?N7sGzuG+i^B zkwo&3FD_?G^|2GrrVK;QdBjQ-2qRt6>@~@?5an<*LM|@#i>|HC=LC(WIeRo@6B7aE zWeyQtk4!X0UnLtzOOg-K`*z7zN|H??_Q2ex)7D1yfau z>kWLd_u4HpaJL+yDytm;X1;Ny2 zIDe-tbOB}0dgjA@B20HArS-}qiEI<8Qssnz>&dR|mhKyQXq?2?27eQABa z&LNj}C^eZlova-j70YJMwD!etVk##l{6Zz1QWgeXwdW>k<;?TmOI-gKgG%$Sl}+z` zyI#pFemWjd5za^_i1pBv)uyCawxP5si!}JeQ2yqrOBQVh;rp7Z98Ng88R+&^zl3?tDKYZK(V|eF1tGT+XY!dgJe}930dOdyr5&1 zG$Qwh&yT*$LXZHqEie*SZw#615)I!G#Tc2z`QJxuZ6spgy%mcz1R7JJ`$o|IH+Q_cD(-bRB zwBbRHzypan)ks)vl(W4e2*&Q-Ag2Ci-4}ZVw$=N?xw5E`=gak@1-&13XX2(SxTkqq zV!p2D$qyS11$`SsX1-PODsMh+%X6*?i>!;4R)Hf^Lq}hTyas;$MD@g#Z{mWgZ1d%U zpt<*l;_@ph4BV8ZSJa@y43uc39W+WI(~o<4*}m!L>+2m*R|l?zg-;$rd8K8WgtlDI zVWm0Cc5&TFW~YP|7HW5mC%U%dr|%#>^zAm!)6Vq% z5HqLoZhaU8E)Y6&LqLEWF7Y>*$}Q;i?Xu7h>TfJiM+={|%Y=*qA3u?0Z6#X%ZO?Pw zNqe-+W22wIK(s}K&*Bfrmf%jU_S@x!qb53pn z9Zi|yknG60g6&5~HyZxXw9SG3{*)#QvbvT>nIn?SKsz47cHayo+3^xlHjOc2^u4!+ z3!yo%X&blFP+RLDMW17Ytz;uNiv`ZK(Kgr%Rez zP&J^1!*%_7$N`ietOl(-A(zS6XA~Qq6g|mjWIAAQXrjMjKI3}k zuFmL&A!WNN_iGg6+W>+S?xiNhu%>tCH9njdJI@7UY(S=7xFJimKYDg zCHCi=8j~cAySu$7549b+GH|WkWVJrGz-UH4{>i0R*W@netdlfc+9PV|&CfhNb0%rb zoz&>bzNH><)Q!5psjugjn~!z-b+5Y1iTsoLOk1Ow(D||fQVZ?Qc_al6DgN$ zo2#14AH|`i1Q>L~bJ3Dp(bGj1URH;z!hRqRU%R=uAqzV1;iTrVTUf`>1i;j2%IJi;c zOh&--{)6F;TK25=hSZS3H|1@)7D?4+D|NXUFJq#Y`JDb zF+Kq2*MC9u>J50#VjIQiNQEZ5KFTP2{qwiobGe!gZmMXZk)ez@6v{Obd-G$KPCKWT z*6f27I5=^WohJXNy9p>veU^F3C~xV~;J9yNQed*SmTZ?YARCgQJH3v0$rk4)gIPHj z9>kmyW(a2n%v7j@cvRnm>E3uM5@QUEto8i6wvJCUqxel9E<{N3Ajgrs#h{x(q3$Sd zpJI-dOfmVUfU4zKIiqZud&AdnD9&NzWRa}YN~`2??E&SBP6e4K2@d&o+4nbUWm7av zqP%?WXo@LW@|Uxfli9>_kty*>k(IM&Dy-7WthKzUNumcJh=$v$9ey!J?XDOaB4|bQ zk6-S_i@wPCC`x&QGK;J!v)p97yiw98*u(+~h0fb@adY=Qv4lRNNLG*u+6pxap2Bi^ zQ36^Z%c12c6w{i^ zjHsDM@oh6z>VZu#+RO+xaX@T%i0)hNh4Dj0HZmBtC>PTxZFcD0?g2Ft?}ABQmGcBV zdqJw!NIG{_2XyZ0KR~J-I$5IMO5>}!WObM4*qMQF93>W}F~B_R#IO~_Jh+`EWjbrI zu9$=#@9GQY@<58Kp9!EhOym1980tX}$nXn-vgS^=5l|5S;yfd$B5en-1Gd-J!$3timKb z2G0yrT0*&?{;cHHtLv2_j!K?Tjz&LV=tj?Kt1=|sgn`%b4k!?Y$0u&w!eN(}s)nz} z%Tt*0#qst45V+_z3R-manoe{_1o`kWf2+49?J7iAnOI0Q1 zzknl#Eq2iEG`s@5joz?Nohp1WWY6A#=cRhQbZYo%n?!M{31s%SHN%|>>uJL_lb2GckL#8>jdbe)KH-SRsVh^HsFpT(` zU!6E2mPxL+^ArLlNAXX(v3t!>d>s|zSH@aqk2Zq#H+x_Jf2 zGexldk@Pvw81^J;!@0bBUMLCnSwREY4#cpD*pR_A!@0k6@*jaa?%rHIR2mKL1>^#1 zc2c$~{OClrLSZWD<$Kg`Q2(6uSc4Gebi6y#lF`@(+J!xSC24?42`ZT;IqM&17HEbZ6ZoE35y-h`d2P0 zj)7e6?E|{)78)@+nSpwH!tG5BjX>$Wp@2B`R11lGekBI;5hsm`jA!4Zsv1Yu>LgKf zGEytJuG+vS>`)TXbdp971PFw!#HMUnSf^VM1s+-%v`h$LokOZEBw6wEoA(#uws69+2X1t?2PB!d7%RHO{Glf7PIK!zv&( zj{vXZnswNqv5VO1<=`U5C_M*E}q$n zgVEVYS?$Ev%J9q#S3EDgtQt`K3UEGOgh|fBbCuTFzxbMuUVNdG0N#**i`hE!NG0H2 z-0PdDrq+XvyKX84Js2?nT<=&hv!5~UdsBG>_AIz-AVhgtvC5iY&g^I9XMWx=*K?&- z1se>xEZJi1ox!=Hj=2ca)xpGaAwy(e;$&Se0cjURp5GLrVkWjunxs6O~bYN5EuIEt~)M-vZAmKIt3N%5~5^g}eZ { return AGG_TYPE[key as keyof typeof AGG_TYPE] === metricAgg; @@ -55,18 +68,7 @@ export function createRegionMapLayerDescriptor({ indexPatternTitle, metricAgg, metricFieldName, -}: { - label: string; - emsLayerId?: string; - leftFieldName?: string; - termsFieldName?: string; - termsSize?: number; - colorSchema: string; - indexPatternId?: string; - indexPatternTitle?: string; - metricAgg: string; - metricFieldName?: string; -}): LayerDescriptor | null { +}: CreateRegionMapLayerDescriptorParams): LayerDescriptor | null { if (!indexPatternId || !emsLayerId || !leftFieldName || !termsFieldName) { return null; } diff --git a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts index e3e5f3878ee568..98217a5f28ad85 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts @@ -33,6 +33,16 @@ import { isMetricCountable } from '../util/is_metric_countable'; const defaultDynamicProperties = getDefaultDynamicProperties(); +export interface CreateTileMapLayerDescriptorParams { + label: string; + mapType: string; + colorSchema: string; + indexPatternId?: string; + geoFieldName?: string; + metricAgg: string; + metricFieldName?: string; +} + function isHeatmap(mapType: string): boolean { return mapType.toLowerCase() === 'heatmap'; } @@ -81,15 +91,7 @@ export function createTileMapLayerDescriptor({ geoFieldName, metricAgg, metricFieldName, -}: { - label: string; - mapType: string; - colorSchema: string; - indexPatternId?: string; - geoFieldName?: string; - metricAgg: string; - metricFieldName?: string; -}): LayerDescriptor | null { +}: CreateTileMapLayerDescriptorParams): LayerDescriptor | null { if (!indexPatternId || !geoFieldName) { return null; } diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index a8b4b48b87989c..2fad5d4eb64ac8 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -50,6 +50,12 @@ export interface Props { settings: MapSettings; layerList: ILayer[]; waitUntilTimeLayersLoad$: Observable; + /* + * Set to false to exclude sharing attributes 'data-*'. + * An example usage is tile_map and region_map visualizations. The visualizations use MapEmbeddable for rendering. + * Visualize Embeddable handles sharing attributes so sharing attributes are not needed in the children. + */ + isSharable: boolean; } interface State { @@ -80,7 +86,11 @@ export class MapContainer extends Component { componentDidUpdate() { this._loadShowFitToBoundsButton(); this._loadShowTimesliderButton(); - if (this.props.areLayersLoaded && !this._isInitalLoadRenderTimerStarted) { + if ( + this.props.isSharable && + this.props.areLayersLoaded && + !this._isInitalLoadRenderTimerStarted + ) { this._isInitalLoadRenderTimerStarted = true; this._startInitialLoadRenderTimer(); } @@ -195,16 +205,18 @@ export class MapContainer extends Component { ); } + const shareAttributes = this.props.isSharable + ? { + ['data-dom-id']: this.state.domId, + ['data-render-complete']: this.state.isInitialLoadRenderTimeoutComplete, + ['data-shared-item']: true, + ['data-title']: this.props.title, + ['data-description']: this.props.description, + } + : {}; + return ( - + + ) => LayerDescriptor[]; + mapCenter?: MapCenterAndZoom; + onInitialRenderComplete?: () => void; + /* + * Set to false to exclude sharing attributes 'data-*'. + */ + isSharable?: boolean; +} + +interface State { + isLoaded: boolean; +} + +export class MapComponent extends Component { + private _isMounted = false; + private _mapEmbeddable?: MapEmbeddableType | undefined; + private readonly _embeddableRef: RefObject = React.createRef(); + + state: State = { isLoaded: false }; + + componentDidMount() { + this._isMounted = true; + this._load(); + } + + componentWillUnmount() { + this._isMounted = false; + if (this._mapEmbeddable) { + this._mapEmbeddable.destroy(); + } + } + + componentDidUpdate() { + if (this._mapEmbeddable) { + this._mapEmbeddable.updateInput({ + filters: this.props.filters, + query: this.props.query, + timeRange: this.props.timeRange, + }); + } + } + + async _load() { + const mapModules = await lazyLoadMapModules(); + if (!this._isMounted) { + return; + } + + this.setState({ isLoaded: true }); + + this._mapEmbeddable = new mapModules.MapEmbeddable( + { + editable: false, + }, + { + id: uuid(), + attributes: { + title: '', + layerListJSON: JSON.stringify([ + mapModules.createBasemapLayerDescriptor(), + ...this.props.getLayerDescriptors({ + createRegionMapLayerDescriptor: mapModules.createRegionMapLayerDescriptor, + createTileMapLayerDescriptor: mapModules.createTileMapLayerDescriptor, + }), + ]), + }, + mapCenter: this.props.mapCenter, + } + ); + this._mapEmbeddable.setOnInitialRenderComplete(this.props.onInitialRenderComplete); + if (this.props.isSharable !== undefined) { + this._mapEmbeddable.setIsSharable(this.props.isSharable); + } + if (this._embeddableRef.current) { + this._mapEmbeddable.render(this._embeddableRef.current); + } + } + + render() { + if (!this.state.isLoaded) { + return ; + } + + return
; + } +} diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 642280aa9dc13f..3255fb6b5e8ee0 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -43,6 +43,7 @@ import { EventHandlers, } from '../reducers/non_serializable_instances'; import { + areLayersLoaded, getGeoFieldNames, getMapCenter, getMapBuffer, @@ -113,6 +114,9 @@ export class MapEmbeddable private _unsubscribeFromStore?: Unsubscribe; private _isInitialized = false; private _controlledBy: string; + private _onInitialRenderComplete?: () => void = undefined; + private _hasInitialRenderCompleteFired = false; + private _isSharable = true; constructor(config: MapEmbeddableConfig, initialInput: MapEmbeddableInput, parent?: IContainer) { super( @@ -231,6 +235,17 @@ export class MapEmbeddable this._savedMap.getStore().dispatch(setEventHandlers(eventHandlers)); }; + public setOnInitialRenderComplete(onInitialRenderComplete?: () => void): void { + this._onInitialRenderComplete = onInitialRenderComplete; + } + + /* + * Set to false to exclude sharing attributes 'data-*'. + */ + public setIsSharable(isSharable: boolean): void { + this._isSharable = isSharable; + } + getInspectorAdapters() { return getInspectorAdapters(this._savedMap.getStore().getState()); } @@ -351,6 +366,7 @@ export class MapEmbeddable title={this.getTitle()} description={this.getDescription()} waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this._savedMap.getStore())} + isSharable={this._isSharable} /> , @@ -510,6 +526,15 @@ export class MapEmbeddable return; } + if ( + this._onInitialRenderComplete && + !this._hasInitialRenderCompleteFired && + areLayersLoaded(this._savedMap.getStore().getState()) + ) { + this._hasInitialRenderCompleteFired = true; + this._onInitialRenderComplete(); + } + const mapExtent = getMapExtent(this._savedMap.getStore().getState()); if (this.input.filterByMapExtent && !_.isEqual(this._prevMapExtent, mapExtent)) { this.setMapExtentFilter(); diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index fd8160c567530c..090a6c8d020431 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -7,6 +7,7 @@ import type { IndexPattern } from '../../../../../src/plugins/data/common/index_patterns'; import { + Embeddable, EmbeddableInput, EmbeddableOutput, SavedObjectEmbeddableInput, @@ -43,3 +44,8 @@ export type MapEmbeddableInput = MapByValueInput | MapByReferenceInput; export type MapEmbeddableOutput = EmbeddableOutput & { indexPatterns: IndexPattern[]; }; + +export type MapEmbeddableType = Embeddable & { + setOnInitialRenderComplete(onInitialRenderComplete?: () => void): void; + setIsSharable(isSharable: boolean): void; +}; diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index abc333ab5e069a..788e5938ee1685 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -8,22 +8,28 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { IndexPatternsContract } from 'src/plugins/data/public/index_patterns'; import { AppMountParameters } from 'kibana/public'; -import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public'; +import { IContainer } from '../../../../../src/plugins/embeddable/public'; import { LayerDescriptor } from '../../common/descriptor_types'; -import { MapEmbeddableConfig, MapEmbeddableInput, MapEmbeddableOutput } from '../embeddable/types'; +import type { + MapEmbeddableConfig, + MapEmbeddableInput, + MapEmbeddableType, +} from '../embeddable/types'; import { SourceRegistryEntry } from '../classes/sources/source_registry'; import { LayerWizard } from '../classes/layers/layer_wizard_registry'; import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_source'; import type { EMSTermJoinConfig, SampleValuesConfig } from '../ems_autosuggest'; +import type { CreateTileMapLayerDescriptorParams } from '../classes/layers/create_tile_map_layer_descriptor'; +import type { CreateRegionMapLayerDescriptorParams } from '../classes/layers/create_region_map_layer_descriptor'; let loadModulesPromise: Promise; -interface LazyLoadedMapModules { +export interface LazyLoadedMapModules { MapEmbeddable: new ( config: MapEmbeddableConfig, initialInput: MapEmbeddableInput, parent?: IContainer - ) => Embeddable; + ) => MapEmbeddableType; getIndexPatternService: () => IndexPatternsContract; getMapsCapabilities: () => any; renderApp: (params: AppMountParameters, AppUsageTracker: React.FC) => Promise<() => void>; @@ -41,15 +47,7 @@ interface LazyLoadedMapModules { geoFieldName, metricAgg, metricFieldName, - }: { - label: string; - mapType: string; - colorSchema: string; - indexPatternId?: string; - geoFieldName?: string; - metricAgg: string; - metricFieldName?: string; - }) => LayerDescriptor | null; + }: CreateTileMapLayerDescriptorParams) => LayerDescriptor | null; createRegionMapLayerDescriptor: ({ label, emsLayerId, @@ -61,18 +59,7 @@ interface LazyLoadedMapModules { indexPatternTitle, metricAgg, metricFieldName, - }: { - label: string; - emsLayerId?: string; - leftFieldName?: string; - termsFieldName?: string; - termsSize?: number; - colorSchema: string; - indexPatternId?: string; - indexPatternTitle?: string; - metricAgg: string; - metricFieldName?: string; - }) => LayerDescriptor | null; + }: CreateRegionMapLayerDescriptorParams) => LayerDescriptor | null; createBasemapLayerDescriptor: () => LayerDescriptor | null; createESSearchSourceLayerDescriptor: (params: CreateLayerDescriptorParams) => LayerDescriptor; suggestEMSTermJoinConfig: (config: SampleValuesConfig) => Promise; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/index.ts b/x-pack/plugins/maps/public/legacy_visualizations/index.ts new file mode 100644 index 00000000000000..a01e0ebefb5ad5 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { createRegionMapFn, regionMapRenderer, regionMapVisType } from './region_map'; +export { createTileMapFn, tileMapRenderer, tileMapVisType } from './tile_map'; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/index.ts b/x-pack/plugins/maps/public/legacy_visualizations/region_map/index.ts new file mode 100644 index 00000000000000..cda57b10887938 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export { regionMapVisType } from './region_map_vis_type'; +export { createRegionMapFn } from './region_map_fn'; +export { regionMapRenderer } from './region_map_renderer'; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx new file mode 100644 index 00000000000000..8830c557f7b4a1 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { VisEditorOptionsProps } from 'src/plugins/visualizations/public'; +import { Vis } from '../../../../../../src/plugins/visualizations/public'; +import { getData, getShareService } from '../../kibana_services'; +import { ViewInMaps } from '../view_in_maps'; +import { extractLayerDescriptorParams } from './utils'; +import { RegionMapVisParams } from './types'; +import { title } from './region_map_vis_type'; + +export function RegionMapEditor(props: VisEditorOptionsProps) { + const onClick = (e: React.MouseEvent) => { + e.preventDefault(); + + const locator = getShareService().url.locators.get('MAPS_APP_REGION_MAP_LOCATOR'); + if (!locator) return; + + const query = getData().query; + locator.navigate({ + ...extractLayerDescriptorParams((props.vis as unknown) as Vis), + filters: query.filterManager.getFilters(), + query: query.queryString.getQuery(), + timeRange: query.timefilter.timefilter.getTime(), + }); + }; + + return ; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_fn.ts b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_fn.ts new file mode 100644 index 00000000000000..f5329aa4212bff --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_fn.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { Filter, Query, TimeRange } from '../../../../../../src/plugins/data/common'; +import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common/search/expressions/kibana_context_type'; +import type { + ExpressionFunctionDefinition, + Render, +} from '../../../../../../src/plugins/expressions/public'; +import { REGION_MAP_RENDER, REGION_MAP_VIS_TYPE, RegionMapVisConfig } from './types'; + +interface Arguments { + visConfig: string; +} + +export interface RegionMapVisRenderValue { + visType: typeof REGION_MAP_VIS_TYPE; + visConfig: RegionMapVisConfig; + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; +} + +export type RegionMapExpressionFunctionDefinition = ExpressionFunctionDefinition< + 'regionmap', + ExpressionValueSearchContext, + Arguments, + Promise> +>; + +export const createRegionMapFn = (): RegionMapExpressionFunctionDefinition => ({ + name: 'regionmap', + type: 'render', + help: i18n.translate('xpack.maps.regionMap.function.help', { + defaultMessage: 'Regionmap visualization', + }), + args: { + visConfig: { + types: ['string'], + default: '"{}"', + help: '', + }, + }, + async fn(input, args) { + return { + type: 'render', + as: REGION_MAP_RENDER, + value: { + visType: REGION_MAP_VIS_TYPE, + visConfig: JSON.parse(args.visConfig), + filters: input.filters, + query: Array.isArray(input.query) ? input.query[0] : input.query, + timeRange: input.timeRange, + }, + }; + }, +}); diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx new file mode 100644 index 00000000000000..1d3531bfed82a0 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import type { ExpressionRenderDefinition } from 'src/plugins/expressions'; +import { RegionMapVisRenderValue } from './region_map_fn'; +import { RegionMapVisualization } from './region_map_visualization'; +import { REGION_MAP_RENDER } from './types'; + +export const regionMapRenderer = { + name: REGION_MAP_RENDER, + reuseDomNode: true, + render: async (domNode, { filters, query, timeRange, visConfig }, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + { + handlers.done(); + }} + filters={filters} + query={query} + timeRange={timeRange} + visConfig={visConfig} + />, + domNode + ); + }, +} as ExpressionRenderDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_vis_type.ts b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_vis_type.ts new file mode 100644 index 00000000000000..4c6e4b2150fb19 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_vis_type.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { VisTypeDefinition } from '../../../../../../src/plugins/visualizations/public'; +import { toExpressionAst } from './to_ast'; +import { REGION_MAP_VIS_TYPE, RegionMapVisParams } from './types'; +import { RegionMapEditor } from './region_map_editor'; + +export const title = i18n.translate('xpack.maps.regionMapMap.vis.title', { + defaultMessage: 'Region Map', +}); + +export const regionMapVisType = { + name: REGION_MAP_VIS_TYPE, + title, + icon: 'visMapRegion', + description: i18n.translate('xpack.maps.regionMap.vis.description', { + defaultMessage: 'Show metrics on a thematic map.', + }), + editorConfig: { + optionTabs: [ + { + name: '', + title: '', + editor: RegionMapEditor, + }, + ], + }, + visConfig: { + defaults: { + colorSchema: 'Yellow to Red', + mapZoom: 2, + mapCenter: [0, 0], + }, + }, + toExpressionAst, + requiresSearch: true, +} as VisTypeDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx new file mode 100644 index 00000000000000..5bb75d781e79b2 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Filter, Query, TimeRange } from '../../../../../../src/plugins/data/common'; +import { RegionMapVisConfig } from './types'; +import type { LazyLoadedMapModules } from '../../lazy_load_bundle'; +import { MapComponent } from '../../embeddable/map_component'; + +interface Props { + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; + visConfig: RegionMapVisConfig; + onInitialRenderComplete: () => void; +} + +export function RegionMapVisualization(props: Props) { + const mapCenter = { + lat: props.visConfig.mapCenter[0], + lon: props.visConfig.mapCenter[1], + zoom: props.visConfig.mapZoom, + }; + function getLayerDescriptors({ + createRegionMapLayerDescriptor, + }: { + createRegionMapLayerDescriptor: LazyLoadedMapModules['createRegionMapLayerDescriptor']; + }) { + const layerDescriptor = createRegionMapLayerDescriptor(props.visConfig.layerDescriptorParams); + return layerDescriptor ? [layerDescriptor] : []; + } + return ( + + ); +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/to_ast.ts b/x-pack/plugins/maps/public/legacy_visualizations/region_map/to_ast.ts new file mode 100644 index 00000000000000..49f431980b950c --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/to_ast.ts @@ -0,0 +1,30 @@ +/* + * 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 { + buildExpression, + buildExpressionFunction, +} from '../../../../../../src/plugins/expressions/public'; +import { VisToExpressionAst } from '../../../../../../src/plugins/visualizations/public'; +import { RegionMapExpressionFunctionDefinition } from './region_map_fn'; +import { RegionMapVisParams } from './types'; +import { extractLayerDescriptorParams } from './utils'; + +export const toExpressionAst: VisToExpressionAst = (vis) => { + const regionMap = buildExpressionFunction('regionmap', { + visConfig: JSON.stringify({ + ...vis.params, + mapCenter: vis.uiState.get('mapCenter', [0, 0]), + mapZoom: parseInt(vis.uiState.get('mapZoom', 2), 10), + layerDescriptorParams: extractLayerDescriptorParams(vis), + }), + }); + + const ast = buildExpression([regionMap]); + + return ast.toAst(); +}; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/types.ts b/x-pack/plugins/maps/public/legacy_visualizations/region_map/types.ts new file mode 100644 index 00000000000000..1ed725798ee581 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/types.ts @@ -0,0 +1,29 @@ +/* + * 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 type { CreateRegionMapLayerDescriptorParams } from '../../classes/layers/create_region_map_layer_descriptor'; + +export const REGION_MAP_RENDER = 'region_map_vis'; +export const REGION_MAP_VIS_TYPE = 'region_map'; + +export interface RegionMapVisParams { + colorSchema: string; + mapZoom: number; + mapCenter: [number, number]; + selectedLayer: { + isEMS: boolean; + id: string | number; + layerId: string; + }; + selectedJoinField: { + name: string; + }; +} + +export interface RegionMapVisConfig extends RegionMapVisParams { + layerDescriptorParams: CreateRegionMapLayerDescriptorParams; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/utils.ts b/x-pack/plugins/maps/public/legacy_visualizations/region_map/utils.ts new file mode 100644 index 00000000000000..a641bbb5a7c39a --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/utils.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Vis } from '../../../../../../src/plugins/visualizations/public'; +import { RegionMapVisParams } from './types'; +import { title } from './region_map_vis_type'; + +function getEmsLayerId(id: string | number, layerId: string) { + if (typeof id === 'string') { + return id; + } + + // Region maps from 6.x will have numerical EMS id refering to S3 bucket id. + // In this case, use layerId with contains the EMS layer name. + const split = layerId.split('.'); + return split.length === 2 ? split[1] : undefined; +} + +export function extractLayerDescriptorParams(vis: Vis) { + const params: { [key: string]: any } = { + label: vis.title ? vis.title : title, + emsLayerId: vis.params.selectedLayer.isEMS + ? getEmsLayerId(vis.params.selectedLayer.id, vis.params.selectedLayer.layerId) + : undefined, + leftFieldName: vis.params.selectedLayer.isEMS ? vis.params.selectedJoinField.name : undefined, + colorSchema: vis.params.colorSchema, + indexPatternId: vis.data.indexPattern?.id, + indexPatternTitle: vis.data.indexPattern?.title, + metricAgg: 'count', + }; + + const bucketAggs = vis.data?.aggs?.byType('buckets'); + if (bucketAggs?.length && bucketAggs[0].type.dslName === 'terms') { + params.termsFieldName = bucketAggs[0].getField()?.name; + params.termsSize = bucketAggs[0].getParam('size'); + } + + const metricAggs = vis.data?.aggs?.byType('metrics'); + if (metricAggs?.length) { + params.metricAgg = metricAggs[0].type.dslName; + params.metricFieldName = metricAggs[0].getField()?.name; + } + + return params; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/index.ts b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/index.ts new file mode 100644 index 00000000000000..04d4c160fb5103 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export { tileMapVisType } from './tile_map_vis_type'; +export { createTileMapFn } from './tile_map_fn'; +export { tileMapRenderer } from './tile_map_renderer'; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx new file mode 100644 index 00000000000000..b177b34a537f97 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { VisEditorOptionsProps } from 'src/plugins/visualizations/public'; +import { Vis } from '../../../../../../src/plugins/visualizations/public'; +import { getData, getShareService } from '../../kibana_services'; +import { ViewInMaps } from '../view_in_maps'; +import { extractLayerDescriptorParams } from './utils'; +import { TileMapVisParams } from './types'; +import { title } from './tile_map_vis_type'; + +export function TileMapEditor(props: VisEditorOptionsProps) { + const onClick = (e: React.MouseEvent) => { + e.preventDefault(); + + const locator = getShareService().url.locators.get('MAPS_APP_TILE_MAP_LOCATOR'); + if (!locator) return; + + const query = getData().query; + locator.navigate({ + ...extractLayerDescriptorParams((props.vis as unknown) as Vis), + filters: query.filterManager.getFilters(), + query: query.queryString.getQuery(), + timeRange: query.timefilter.timefilter.getTime(), + }); + }; + + return ; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_fn.ts b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_fn.ts new file mode 100644 index 00000000000000..96c962be0c31bc --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_fn.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { Filter, Query, TimeRange } from '../../../../../../src/plugins/data/common'; +import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common/search/expressions/kibana_context_type'; +import type { + ExpressionFunctionDefinition, + Render, +} from '../../../../../../src/plugins/expressions/public'; +import { TILE_MAP_RENDER, TILE_MAP_VIS_TYPE, TileMapVisConfig } from './types'; + +interface Arguments { + visConfig: string; +} + +export interface TileMapVisRenderValue { + visType: typeof TILE_MAP_VIS_TYPE; + visConfig: TileMapVisConfig; + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; +} + +export type TileMapExpressionFunctionDefinition = ExpressionFunctionDefinition< + 'tilemap', + ExpressionValueSearchContext, + Arguments, + Promise> +>; + +export const createTileMapFn = (): TileMapExpressionFunctionDefinition => ({ + name: 'tilemap', + type: 'render', + help: i18n.translate('xpack.maps.tileMap.function.help', { + defaultMessage: 'Tilemap visualization', + }), + args: { + visConfig: { + types: ['string'], + default: '"{}"', + help: '', + }, + }, + async fn(input, args) { + return { + type: 'render', + as: TILE_MAP_RENDER, + value: { + visType: TILE_MAP_VIS_TYPE, + visConfig: JSON.parse(args.visConfig), + filters: input.filters, + query: Array.isArray(input.query) ? input.query[0] : input.query, + timeRange: input.timeRange, + }, + }; + }, +}); diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx new file mode 100644 index 00000000000000..5e61a0e0cd3682 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import type { ExpressionRenderDefinition } from 'src/plugins/expressions'; +import { TileMapVisRenderValue } from './tile_map_fn'; +import { TileMapVisualization } from './tile_map_visualization'; +import { TILE_MAP_RENDER } from './types'; + +export const tileMapRenderer = { + name: TILE_MAP_RENDER, + reuseDomNode: true, + render: async (domNode, { filters, query, timeRange, visConfig }, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + { + handlers.done(); + }} + filters={filters} + query={query} + timeRange={timeRange} + visConfig={visConfig} + />, + domNode + ); + }, +} as ExpressionRenderDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_vis_type.ts b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_vis_type.ts new file mode 100644 index 00000000000000..458adcab8c8d13 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_vis_type.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { VisTypeDefinition } from '../../../../../../src/plugins/visualizations/public'; +import { toExpressionAst } from './to_ast'; +import { MapTypes, TileMapVisParams, TILE_MAP_VIS_TYPE } from './types'; +import { TileMapEditor } from './tile_map_editor'; + +export const title = i18n.translate('xpack.maps.tileMap.vis.title', { + defaultMessage: 'Coordinate Map', +}); + +export const tileMapVisType = { + name: TILE_MAP_VIS_TYPE, + title, + icon: 'visMapCoordinate', + description: i18n.translate('xpack.maps.tileMap.vis.description', { + defaultMessage: 'Plot latitude and longitude coordinates on a map', + }), + editorConfig: { + optionTabs: [ + { + name: '', + title: '', + editor: TileMapEditor, + }, + ], + }, + visConfig: { + defaults: { + colorSchema: 'Yellow to Red', + mapType: MapTypes.ScaledCircleMarkers, + mapZoom: 2, + mapCenter: [0, 0], + }, + }, + toExpressionAst, + requiresSearch: true, +} as VisTypeDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx new file mode 100644 index 00000000000000..225b29de5652bc --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Filter, Query, TimeRange } from '../../../../../../src/plugins/data/common'; +import { TileMapVisConfig } from './types'; +import type { LazyLoadedMapModules } from '../../lazy_load_bundle'; +import { MapComponent } from '../../embeddable/map_component'; + +interface Props { + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; + visConfig: TileMapVisConfig; + onInitialRenderComplete: () => void; +} + +export function TileMapVisualization(props: Props) { + const mapCenter = { + lat: props.visConfig.mapCenter[0], + lon: props.visConfig.mapCenter[1], + zoom: props.visConfig.mapZoom, + }; + function getLayerDescriptors({ + createTileMapLayerDescriptor, + }: { + createTileMapLayerDescriptor: LazyLoadedMapModules['createTileMapLayerDescriptor']; + }) { + const layerDescriptor = createTileMapLayerDescriptor(props.visConfig.layerDescriptorParams); + return layerDescriptor ? [layerDescriptor] : []; + } + return ( + + ); +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/to_ast.ts b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/to_ast.ts new file mode 100644 index 00000000000000..5417dabfee8d44 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/to_ast.ts @@ -0,0 +1,30 @@ +/* + * 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 { + buildExpression, + buildExpressionFunction, +} from '../../../../../../src/plugins/expressions/public'; +import { VisToExpressionAst } from '../../../../../../src/plugins/visualizations/public'; +import { TileMapExpressionFunctionDefinition } from './tile_map_fn'; +import { TileMapVisParams } from './types'; +import { extractLayerDescriptorParams } from './utils'; + +export const toExpressionAst: VisToExpressionAst = (vis) => { + const tileMap = buildExpressionFunction('tilemap', { + visConfig: JSON.stringify({ + ...vis.params, + mapCenter: vis.uiState.get('mapCenter', [0, 0]), + mapZoom: parseInt(vis.uiState.get('mapZoom', 2), 10), + layerDescriptorParams: extractLayerDescriptorParams(vis), + }), + }); + + const ast = buildExpression([tileMap]); + + return ast.toAst(); +}; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/types.ts b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/types.ts new file mode 100644 index 00000000000000..4e65fb82b797d2 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/types.ts @@ -0,0 +1,29 @@ +/* + * 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 type { CreateTileMapLayerDescriptorParams } from '../../classes/layers/create_tile_map_layer_descriptor'; + +export const TILE_MAP_RENDER = 'tile_map_vis'; +export const TILE_MAP_VIS_TYPE = 'tile_map'; + +export enum MapTypes { + ScaledCircleMarkers = 'Scaled Circle Markers', + ShadedCircleMarkers = 'Shaded Circle Markers', + ShadedGeohashGrid = 'Shaded Geohash Grid', + Heatmap = 'Heatmap', +} + +export interface TileMapVisParams { + colorSchema: string; + mapType: MapTypes; + mapZoom: number; + mapCenter: [number, number]; +} + +export interface TileMapVisConfig extends TileMapVisParams { + layerDescriptorParams: CreateTileMapLayerDescriptorParams; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/utils.ts b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/utils.ts new file mode 100644 index 00000000000000..3fcb3d89157019 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/utils.ts @@ -0,0 +1,44 @@ +/* + * 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 { Vis } from '../../../../../../src/plugins/visualizations/public'; +import { indexPatterns } from '../../../../../../src/plugins/data/public'; +import { TileMapVisParams } from './types'; +import { title } from './tile_map_vis_type'; + +export function extractLayerDescriptorParams(vis: Vis) { + const params: { [key: string]: any } = { + label: vis.title ? vis.title : title, + mapType: vis.params.mapType, + colorSchema: vis.params.colorSchema, + indexPatternId: vis.data.indexPattern?.id, + metricAgg: 'count', + }; + + const bucketAggs = vis.data?.aggs?.byType('buckets'); + if (bucketAggs?.length && bucketAggs[0].type.dslName === 'geohash_grid') { + params.geoFieldName = bucketAggs[0].getField()?.name; + } else if (vis.data.indexPattern) { + // attempt to default to first geo point field when geohash is not configured yet + const geoField = vis.data.indexPattern.fields.find((field) => { + return ( + !indexPatterns.isNestedField(field) && field.aggregatable && field.type === 'geo_point' + ); + }); + if (geoField) { + params.geoFieldName = geoField.name; + } + } + + const metricAggs = vis.data?.aggs?.byType('metrics'); + if (metricAggs?.length) { + params.metricAgg = metricAggs[0].type.dslName; + params.metricFieldName = metricAggs[0].getField()?.name; + } + + return params; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/view_in_maps.tsx b/x-pack/plugins/maps/public/legacy_visualizations/view_in_maps.tsx new file mode 100644 index 00000000000000..39d959865a9a00 --- /dev/null +++ b/x-pack/plugins/maps/public/legacy_visualizations/view_in_maps.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButton, EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +interface Props { + onClick: (e: React.MouseEvent) => void; + visualizationLabel: string; +} + +export function ViewInMaps(props: Props) { + return ( + +

+ +

+

+ +

+
+ + + +
+
+ ); +} diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 3253078c8c11b6..4ee2a83589c95e 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -36,6 +36,7 @@ import type { VisualizationsSetup, VisualizationsStart, } from '../../../../src/plugins/visualizations/public'; +import type { Plugin as ExpressionsPublicPlugin } from '../../../../src/plugins/expressions/public'; import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public'; import { visualizeGeoFieldAction } from './trigger_actions/visualize_geo_field_action'; @@ -73,9 +74,18 @@ import { MapsAppRegionMapLocatorDefinition, MapsAppTileMapLocatorDefinition, } from './locators'; +import { + createRegionMapFn, + regionMapRenderer, + regionMapVisType, + createTileMapFn, + tileMapRenderer, + tileMapVisType, +} from './legacy_visualizations'; import { SecurityPluginStart } from '../../security/public'; export interface MapsPluginSetupDependencies { + expressions: ReturnType; inspector: InspectorSetupContract; home?: HomePublicPluginSetup; visualizations: VisualizationsSetup; @@ -177,6 +187,14 @@ export class MapsPlugin return renderApp(params, UsageTracker); }, }); + + // register wrapper around legacy tile_map and region_map visualizations + plugins.expressions.registerFunction(createRegionMapFn); + plugins.expressions.registerRenderer(regionMapRenderer); + plugins.visualizations.createBaseVisualization(regionMapVisType); + plugins.expressions.registerFunction(createTileMapFn); + plugins.expressions.registerRenderer(tileMapRenderer); + plugins.visualizations.createBaseVisualization(tileMapVisType); } public start(core: CoreStart, plugins: MapsPluginStartDependencies): MapsStartApi { diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index 5231aab5d11948..4a2b0fbefad681 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -456,6 +456,7 @@ export class MapApp extends React.Component { title={this.props.savedMap.getAttributes().title} description={this.props.savedMap.getAttributes().description} waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this.props.savedMap.getStore())} + isSharable />
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index eae33aa422ea1c..6e80cb781e9a64 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3204,37 +3204,6 @@ "management.stackManagement.managementDescription": "Elastic Stack の管理を行うセンターコンソールです。", "management.stackManagement.managementLabel": "スタック管理", "management.stackManagement.title": "スタック管理", - "maps_legacy.advancedSettings.visualization.tileMap.maxPrecision.cellDimensionsLinkText": "ディメンションの説明", - "maps_legacy.advancedSettings.visualization.tileMap.maxPrecisionText": "タイルマップに表示されるジオハッシュの最高精度です。7は高い、10は非常に高い、12は最大です。{cellDimensionsLink}", - "maps_legacy.advancedSettings.visualization.tileMap.maxPrecisionTitle": "タイルマップの最高精度", - "maps_legacy.advancedSettings.visualization.tileMap.wmsDefaults.propertiesLinkText": "プロパティ", - "maps_legacy.advancedSettings.visualization.tileMap.wmsDefaultsText": "座標マップのWMSマップサーバーサポートのデフォルトの{propertiesLink}です。", - "maps_legacy.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "デフォルトのWMSプロパティ", - "maps_legacy.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子はdata-updateに対応できるようこのメソッドを導入する必要があります", - "maps_legacy.defaultDistributionMessage": "Mapsを入手するには、ElasticsearchとKibanaの{defaultDistribution}にアップグレードしてください。", - "maps_legacy.kibanaMap.leaflet.fitDataBoundsAriaLabel": "データバウンドを合わせる", - "maps_legacy.legacyMapDeprecationMessage": "Mapsを使用すると、複数のレイヤーとインデックスを追加する、個別のドキュメントをプロットする、データ値から特徴を表現する、ヒートマップ、グリッド、クラスターを追加するなど、さまざまなことが可能です。{getMapsMessage}", - "maps_legacy.legacyMapDeprecationTitle": "{label}は8.0でMapsに移行されます。", - "maps_legacy.openInMapsButtonLabel": "Mapsで表示", - "maps_legacy.wmsOptions.attributionStringTip": "右下角の属性文字列", - "maps_legacy.wmsOptions.baseLayerSettingsTitle": "ベースレイヤー設定", - "maps_legacy.wmsOptions.imageFormatToUseTip": "通常、画像/pngまたは画像/jpegです。サーバーが透明レイヤーを返す場合は。pngを使用します。", - "maps_legacy.wmsOptions.layersLabel": "レイヤー", - "maps_legacy.wmsOptions.listOfLayersToUseTip": "使用するレイヤーのコンマ区切りのリストです。", - "maps_legacy.wmsOptions.mapLoadFailDescription": "このパラメーターが正しくないと、マップが正常に読み込まれません。", - "maps_legacy.wmsOptions.urlOfWMSWebServiceTip": "WMS WebサービスのURLです。", - "maps_legacy.wmsOptions.useWMSCompliantMapTileServerTip": "WMS対応のマップタイルサーバーを使用します。上級者向けです。", - "maps_legacy.wmsOptions.versionOfWMSserverSupportsTip": "サーバーがサポートしているWMSのバージョンです。", - "maps_legacy.wmsOptions.wmsAttributionLabel": "WMS属性", - "maps_legacy.wmsOptions.wmsDescription": "WMSは、マップイメージサービスの{wmsLink}です。", - "maps_legacy.wmsOptions.wmsFormatLabel": "WMSフォーマット", - "maps_legacy.wmsOptions.wmsLayersLabel": "WMSレイヤー", - "maps_legacy.wmsOptions.wmsLinkText": "OGCスタンダード", - "maps_legacy.wmsOptions.wmsMapServerLabel": "WMSマップサーバー", - "maps_legacy.wmsOptions.wmsServerSupportedStylesListTip": "WMSサーバーがサポートしている、使用するスタイルのコンマ区切りのリストです。一般的に、空白のままです。", - "maps_legacy.wmsOptions.wmsStylesLabel": "WMSスタイル", - "maps_legacy.wmsOptions.wmsUrlLabel": "WMS URL", - "maps_legacy.wmsOptions.wmsVersionLabel": "WMS バージョン", "monaco.painlessLanguage.autocomplete.docKeywordDescription": "doc['field_name'] 構文を使用して、スクリプトからフィールド値にアクセスします", "monaco.painlessLanguage.autocomplete.emitKeywordDescription": "戻らずに値を発行します。", "monaco.painlessLanguage.autocomplete.fieldValueDescription": "フィールド「{fieldName}」の値を取得します", @@ -3281,30 +3250,6 @@ "presentationUtil.solutionToolbar.libraryButtonLabel": "ライブラリから追加", "presentationUtil.solutionToolbar.quickButton.ariaButtonLabel": "新しい{createType}を作成", "presentationUtil.solutionToolbar.quickButton.legendLabel": "クイック作成", - "regionMap.advancedSettings.visualization.showRegionMapWarningsText": "用語がマップの形に合わない場合に地域マップに警告を表示するかどうかです。", - "regionMap.advancedSettings.visualization.showRegionMapWarningsTitle": "地域マップに警告を表示", - "regionMap.choroplethLayer.downloadingVectorData404ErrorMessage": "{name} の取得時にサーバーから「404」が返されます。指定された場所にファイルが存在することを確認してください。", - "regionMap.choroplethLayer.downloadingVectorDataErrorMessage": "{name} ファイルをダウンロードできません。サーバーの CORS 構成で、このホストの Kibana アプリケーションからのリクエストが許可されていることを確認してください。", - "regionMap.choroplethLayer.downloadingVectorDataErrorMessageTitle": "ベクトルデータのダウンロード中にエラーが発生しました", - "regionMap.choroplethLayer.unrecognizedFormatErrorMessage": "認識されないフォーマット {formatType}", - "regionMap.function.help": "地域マップビジュアライゼーション", - "regionMap.mapVis.regionMapDescription": "テーママップにメトリックを表示します。提供されたベースマップを使用するか、独自のマップを追加できます。暗い色は大きな値を意味します。", - "regionMap.mapVis.regionMapEditorConfig.schemas.metricTitle": "値", - "regionMap.mapVis.regionMapEditorConfig.schemas.segmentTitle": "フィールドのシェイプ", - "regionMap.mapVis.regionMapTitle": "地域マップ", - "regionMap.visParams.colorSchemaLabel": "配色", - "regionMap.visParams.displayWarningsLabel": "警告を表示", - "regionMap.visParams.joinFieldLabel": "フィールドを結合", - "regionMap.visParams.layerSettingsTitle": "レイヤー設定", - "regionMap.visParams.outlineWeightLabel": "境界の太さ", - "regionMap.visParams.previewOnEMSLinkText": "EMS でプレビュー", - "regionMap.visParams.previewOnEMSLinkTitle": "Elastic Maps Service で {selectedLayerName} をプレビュー", - "regionMap.visParams.showAllShapesLabel": "すべてのシェイプを表示", - "regionMap.visParams.styleSettingsLabel": "スタイル設定", - "regionMap.visParams.switchWarningsTipText": "警告のオン/オフを切り替えます。オンの場合、結合フィールドに基づきベクトルレイヤーのシェイプと一致しない用語ごとに警告が表示されます。オフにすると、これらの警告がオフになります。", - "regionMap.visParams.turnOffShowingAllShapesTipText": "この設定をオフにすると、対応する用語と一致したシェイプのみが表示されます。", - "regionMap.visParams.vectorMapLabel": "ベクトルマップ", - "regionMap.visualization.unableToShowMismatchesWarningText": "次の各用語がシェイプの結合フィールドのシェイプと一致することを確認してください:{mismatches}", "savedObjects.advancedSettings.listingLimitText": "一覧ページ用に取得するオブジェクトの数です", "savedObjects.advancedSettings.listingLimitTitle": "オブジェクト取得制限", "savedObjects.advancedSettings.perPageText": "読み込みダイアログで表示されるページごとのオブジェクトの数です", @@ -3548,27 +3493,6 @@ "telemetry.welcomeBanner.enableButtonLabel": "有効にする", "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "プライバシーポリシー", "telemetry.welcomeBanner.title": "Elastic Stack の改善にご協力ください", - "tileMap.function.help": "タイルマップのビジュアライゼーションです", - "tileMap.geohashLayer.mapTitle": "{mapType} マップタイプが認識されません", - "tileMap.legendPositions.bottomLeftText": "左下", - "tileMap.legendPositions.bottomRightText": "右下", - "tileMap.legendPositions.topLeftText": "左上", - "tileMap.legendPositions.topRightText": "右上", - "tileMap.mapTypes.heatmapText": "ヒートマップ", - "tileMap.mapTypes.scaledCircleMarkersText": "スケーリングされた円マーカー", - "tileMap.mapTypes.shadedCircleMarkersText": "影付き円マーカー", - "tileMap.mapTypes.shadedGeohashGridText": "影付きジオハッシュグリッド", - "tileMap.tooltipFormatter.latitudeLabel": "緯度", - "tileMap.tooltipFormatter.longitudeLabel": "経度", - "tileMap.vis.map.editorConfig.schemas.geoCoordinatesTitle": "座標", - "tileMap.vis.map.editorConfig.schemas.metricTitle": "値", - "tileMap.vis.mapDescription": "マップ上に緯度と経度の座標を表示します。", - "tileMap.vis.mapTitle": "座標マップ", - "tileMap.visParams.clusterSizeLabel": "クラスターサイズ", - "tileMap.visParams.colorSchemaLabel": "配色", - "tileMap.visParams.desaturateTilesLabel": "タイルを不飽和化", - "tileMap.visParams.mapTypeLabel": "マップタイプ", - "tileMap.visParams.reduceVibrancyOfTileColorsTip": "色の鮮明度を下げます。この機能は Internet Explorer ではバージョンにかかわらず利用できません。", "timelion.badge.readOnly.text": "読み取り専用", "timelion.badge.readOnly.tooltip": "Timelion シートを保存できません", "timelion.breadcrumbs.create": "作成", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 08acc43b1b9a55..5687f9e4a2f114 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3218,37 +3218,6 @@ "management.stackManagement.managementDescription": "您用于管理 Elastic Stack 的中心控制台。", "management.stackManagement.managementLabel": "Stack Management", "management.stackManagement.title": "Stack Management", - "maps_legacy.advancedSettings.visualization.tileMap.maxPrecision.cellDimensionsLinkText": "单元格维度的解释", - "maps_legacy.advancedSettings.visualization.tileMap.maxPrecisionText": "在磁贴地图上显示的最大 geoHash 精度:7 代表较高,10 代表非常高,12 代表最大值。{cellDimensionsLink}", - "maps_legacy.advancedSettings.visualization.tileMap.maxPrecisionTitle": "最大磁贴地图精度", - "maps_legacy.advancedSettings.visualization.tileMap.wmsDefaults.propertiesLinkText": "属性", - "maps_legacy.advancedSettings.visualization.tileMap.wmsDefaultsText": "坐标地图中支持 WMS 地图服务器的默认{propertiesLink}", - "maps_legacy.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "默认 WMS 属性", - "maps_legacy.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子对象应实现此方法以响应数据更新", - "maps_legacy.defaultDistributionMessage": "要获取 Maps,请升级到 {defaultDistribution} 版的 Elasticsearch 和 Kibana。", - "maps_legacy.kibanaMap.leaflet.fitDataBoundsAriaLabel": "适应数据边界", - "maps_legacy.legacyMapDeprecationMessage": "使用 Maps,可以添加多个图层和索引,绘制单个文档,使用数据值表示特征,添加热图、网格和集群,等等。{getMapsMessage}", - "maps_legacy.legacyMapDeprecationTitle": "在 8.0 中,{label} 将迁移到 Maps。", - "maps_legacy.openInMapsButtonLabel": "在 Maps 中查看", - "maps_legacy.wmsOptions.attributionStringTip": "右下角的归因字符串。", - "maps_legacy.wmsOptions.baseLayerSettingsTitle": "基础图层设置", - "maps_legacy.wmsOptions.imageFormatToUseTip": "通常为 image/png 或 image/jpeg。如果服务器将返回透明图层,则请使用 png。", - "maps_legacy.wmsOptions.layersLabel": "图层", - "maps_legacy.wmsOptions.listOfLayersToUseTip": "要使用的图层逗号分隔列表。", - "maps_legacy.wmsOptions.mapLoadFailDescription": "如果此参数不正确,将无法加载地图。", - "maps_legacy.wmsOptions.urlOfWMSWebServiceTip": "WMS Web 服务的 URL。", - "maps_legacy.wmsOptions.useWMSCompliantMapTileServerTip": "使用符合 WMS 规范的地图磁贴服务器。仅适用于高级用户。", - "maps_legacy.wmsOptions.versionOfWMSserverSupportsTip": "服务器支持的 WMS 版本。", - "maps_legacy.wmsOptions.wmsAttributionLabel": "WMS 属性", - "maps_legacy.wmsOptions.wmsDescription": "WMS 是用于地图图像服务的 {wmsLink}。", - "maps_legacy.wmsOptions.wmsFormatLabel": "WMS 格式", - "maps_legacy.wmsOptions.wmsLayersLabel": "WMS 图层", - "maps_legacy.wmsOptions.wmsLinkText": "OGC 标准", - "maps_legacy.wmsOptions.wmsMapServerLabel": "WMS 地图服务器", - "maps_legacy.wmsOptions.wmsServerSupportedStylesListTip": "要使用的 WMS 服务器支持的样式逗号分隔列表。在大部分情况下为空。", - "maps_legacy.wmsOptions.wmsStylesLabel": "WMS 样式", - "maps_legacy.wmsOptions.wmsUrlLabel": "WMS url", - "maps_legacy.wmsOptions.wmsVersionLabel": "WMS 版本", "monaco.painlessLanguage.autocomplete.docKeywordDescription": "使用 doc['field_name'] 语法,从脚本中访问字段值", "monaco.painlessLanguage.autocomplete.emitKeywordDescription": "发出值,而不返回值。", "monaco.painlessLanguage.autocomplete.fieldValueDescription": "检索字段“{fieldName}”的值", @@ -3295,31 +3264,6 @@ "presentationUtil.solutionToolbar.libraryButtonLabel": "从库中添加", "presentationUtil.solutionToolbar.quickButton.ariaButtonLabel": "创建新的 {createType}", "presentationUtil.solutionToolbar.quickButton.legendLabel": "快速创建", - "regionMap.advancedSettings.visualization.showRegionMapWarningsText": "词无法联接到地图上的形状时,区域地图是否显示警告。", - "regionMap.advancedSettings.visualization.showRegionMapWarningsTitle": "显示区域地图警告", - "regionMap.choroplethLayer.downloadingVectorData404ErrorMessage": "尝试提取 {name} 时,服务器响应“404”。请确保文件位于该位置。", - "regionMap.choroplethLayer.downloadingVectorDataErrorMessage": "无法下载 {name} 文件。请确保服务器的 CORS 配置允许来自此主机上的 Kibana 应用程序的请求。", - "regionMap.choroplethLayer.downloadingVectorDataErrorMessageTitle": "下载矢量数据时出错", - "regionMap.choroplethLayer.unrecognizedFormatErrorMessage": "格式 {formatType} 无法识别", - "regionMap.function.help": "地区地图可视化", - "regionMap.mapVis.regionMapDescription": "在主题地图上显示指标。使用一个已提供的基础地图,或添加自己的地图。颜色越深表示值越大。", - "regionMap.mapVis.regionMapEditorConfig.schemas.metricTitle": "值", - "regionMap.mapVis.regionMapEditorConfig.schemas.segmentTitle": "形状字段", - "regionMap.mapVis.regionMapTitle": "区域地图", - "regionMap.visParams.colorSchemaLabel": "颜色方案", - "regionMap.visParams.displayWarningsLabel": "显示警告", - "regionMap.visParams.joinFieldLabel": "联接字段", - "regionMap.visParams.layerSettingsTitle": "图层设置", - "regionMap.visParams.outlineWeightLabel": "边框粗细", - "regionMap.visParams.previewOnEMSLinkText": "在 EMS 上预览", - "regionMap.visParams.previewOnEMSLinkTitle": "在 Elastic Maps Service 上预览“{selectedLayerName}”", - "regionMap.visParams.showAllShapesLabel": "显示所有形状", - "regionMap.visParams.styleSettingsLabel": "样式设置", - "regionMap.visParams.switchWarningsTipText": "打开/关闭警告。打开后,将对根据联接字段与矢量图层中的形状不匹配的每个字词显示警告。关闭后,将不显示这些警告。", - "regionMap.visParams.turnOffShowingAllShapesTipText": "关闭此设置时,将仅显示与相应字词匹配的形状。", - "regionMap.visParams.vectorMapLabel": "矢量地图", - "regionMap.visualization.unableToShowMismatchesWarningText": "确保每个字词与该形状的联接字段匹配:{mismatches}", - "regionMap.visualization.unableToShowMismatchesWarningTitle": "无法在地图上显示 {mismatchesLength} 个{oneMismatch, plural, other {结果}}", "savedObjects.advancedSettings.listingLimitText": "要为列表页面提取的对象数目", "savedObjects.advancedSettings.listingLimitTitle": "对象列表限制", "savedObjects.advancedSettings.perPageText": "加载对话框中每页要显示的对象数目", @@ -3568,27 +3512,6 @@ "telemetry.welcomeBanner.enableButtonLabel": "启用", "telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "隐私声明", "telemetry.welcomeBanner.title": "帮助我们改进 Elastic Stack", - "tileMap.function.help": "磁贴地图可视化", - "tileMap.geohashLayer.mapTitle": "{mapType} 地图类型无法识别", - "tileMap.legendPositions.bottomLeftText": "左下方", - "tileMap.legendPositions.bottomRightText": "右下方", - "tileMap.legendPositions.topLeftText": "左上方", - "tileMap.legendPositions.topRightText": "右上方", - "tileMap.mapTypes.heatmapText": "热图", - "tileMap.mapTypes.scaledCircleMarkersText": "缩放式圆形标记", - "tileMap.mapTypes.shadedCircleMarkersText": "带阴影圆形标记", - "tileMap.mapTypes.shadedGeohashGridText": "带阴影 geohash 网格", - "tileMap.tooltipFormatter.latitudeLabel": "纬度", - "tileMap.tooltipFormatter.longitudeLabel": "经度", - "tileMap.vis.map.editorConfig.schemas.geoCoordinatesTitle": "地理坐标", - "tileMap.vis.map.editorConfig.schemas.metricTitle": "值", - "tileMap.vis.mapDescription": "在地图上绘制纬度和经度坐标", - "tileMap.vis.mapTitle": "坐标地图", - "tileMap.visParams.clusterSizeLabel": "集群大小", - "tileMap.visParams.colorSchemaLabel": "颜色方案", - "tileMap.visParams.desaturateTilesLabel": "降低平铺地图饱和度", - "tileMap.visParams.mapTypeLabel": "地图类型", - "tileMap.visParams.reduceVibrancyOfTileColorsTip": "降低磁贴颜色的鲜艳度。此设置在任何版本的 Internet Explorer 中均不起作用。", "timelion.badge.readOnly.text": "只读", "timelion.badge.readOnly.tooltip": "无法保存 Timelion 工作表", "timelion.breadcrumbs.create": "创建", diff --git a/x-pack/test/functional/apps/maps/visualize_create_menu.js b/x-pack/test/functional/apps/maps/visualize_create_menu.js index c9044353fbde85..795480ec1c6387 100644 --- a/x-pack/test/functional/apps/maps/visualize_create_menu.js +++ b/x-pack/test/functional/apps/maps/visualize_create_menu.js @@ -64,12 +64,12 @@ export default function ({ getService, getPageObjects }) { }); it('should not show legacy region map visualizion in create menu', async () => { - const hasLegecyViz = await PageObjects.visualize.hasRegionMap(); + const hasLegecyViz = await PageObjects.visualize.hasVisType('region_map'); expect(hasLegecyViz).to.equal(false); }); it('should not show legacy tilemap map visualizion in create menu', async () => { - const hasLegecyViz = await PageObjects.visualize.hasTileMap(); + const hasLegecyViz = await PageObjects.visualize.hasVisType('tile_map'); expect(hasLegecyViz).to.equal(false); }); }); diff --git a/yarn.lock b/yarn.lock index eaf99ed839ceaf..84246830a2fffa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18275,26 +18275,6 @@ lead@^1.0.0: dependencies: flush-write-stream "^1.0.2" -leaflet-draw@0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/leaflet-draw/-/leaflet-draw-0.4.14.tgz#1b5b06d570873a015aa96b80d664dab496c45a4a" - integrity sha512-O99KSPjyHNMDE+uD/fpdPydvQfTE8QruaG7ylEEtKCIaSSb60mCWoDdGUqGEHU9PGPu2JxbEfkGTXY9eYv7aEw== - -leaflet-responsive-popup@0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/leaflet-responsive-popup/-/leaflet-responsive-popup-0.6.4.tgz#b93d9368ef9f96d6dc911cf5b96d90e08601c6b3" - integrity sha512-2D8G9aQA6NHkulDBPN9kqbUCkCpWQQ6dF0xFL11AuEIWIbsL4UC/ZPP5m8GYM0dpU6YTlmyyCh1Tz+cls5Q4dg== - -leaflet.heat@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/leaflet.heat/-/leaflet.heat-0.2.0.tgz#109d8cf586f0adee41f05aff031e27a77fecc229" - integrity sha1-EJ2M9Ybwre5B8Fr/Ax4np3/swik= - -leaflet@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.5.1.tgz#9afb9d963d66c870066b1342e7a06f92840f46bf" - integrity sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w== - "less@npm:@elastic/less@2.7.3-kibana": version "2.7.3-kibana" resolved "https://registry.yarnpkg.com/@elastic/less/-/less-2.7.3-kibana.tgz#3de5e0b06bb095b1cc1149043d67f8dc36272d23" From 51c15945f262524203c5f563668e9baf807c6047 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Aug 2021 20:51:52 +0100 Subject: [PATCH 19/36] chore(NA): moving @kbn/mapbox-gl to babel transpiler (#109082) * chore(NA): moving @kbn/mapbox-gl to babel transpiler * chore(NA): structure exports Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-mapbox-gl/.babelrc | 3 +++ packages/kbn-mapbox-gl/BUILD.bazel | 20 ++++++++++++++------ packages/kbn-mapbox-gl/package.json | 4 ++-- packages/kbn-mapbox-gl/src/index.ts | 5 +++-- packages/kbn-mapbox-gl/tsconfig.json | 3 ++- 5 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 packages/kbn-mapbox-gl/.babelrc diff --git a/packages/kbn-mapbox-gl/.babelrc b/packages/kbn-mapbox-gl/.babelrc new file mode 100644 index 00000000000000..7da72d17791281 --- /dev/null +++ b/packages/kbn-mapbox-gl/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"] +} diff --git a/packages/kbn-mapbox-gl/BUILD.bazel b/packages/kbn-mapbox-gl/BUILD.bazel index 3cbf7c39421e28..e0de6848c22899 100644 --- a/packages/kbn-mapbox-gl/BUILD.bazel +++ b/packages/kbn-mapbox-gl/BUILD.bazel @@ -1,6 +1,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-mapbox-gl" PKG_REQUIRE_NAME = "@kbn/mapbox-gl" @@ -26,17 +27,23 @@ NPM_MODULE_EXTRA_FILES = [ "README.md" ] -SRC_DEPS = [ +RUNTIME_DEPS = [ "@npm//@mapbox/mapbox-gl-rtl-text", "@npm//file-loader", "@npm//mapbox-gl", ] TYPES_DEPS = [ + "@npm//@mapbox/mapbox-gl-rtl-text", + "@npm//file-loader", "@npm//@types/mapbox-gl", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) ts_config( name = "tsconfig", @@ -48,13 +55,14 @@ ts_config( ) ts_project( - name = "tsc", + name = "tsc_types", args = ['--pretty'], srcs = SRCS, - deps = DEPS, + deps = TYPES_DEPS, declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", source_map = True, root_dir = "src", tsconfig = ":tsconfig", @@ -63,7 +71,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = DEPS + [":tsc"], + deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) diff --git a/packages/kbn-mapbox-gl/package.json b/packages/kbn-mapbox-gl/package.json index 9de88dac54a5ab..fef881f2631daf 100644 --- a/packages/kbn-mapbox-gl/package.json +++ b/packages/kbn-mapbox-gl/package.json @@ -3,6 +3,6 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target/index.js", - "types": "./target/index.d.ts" + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts" } diff --git a/packages/kbn-mapbox-gl/src/index.ts b/packages/kbn-mapbox-gl/src/index.ts index 404684af780310..87b85002d7598c 100644 --- a/packages/kbn-mapbox-gl/src/index.ts +++ b/packages/kbn-mapbox-gl/src/index.ts @@ -35,8 +35,9 @@ import 'mapbox-gl/dist/mapbox-gl.css'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); -export { - mapboxgl, +export { mapboxgl }; + +export type { Map, GeoJSONSource, VectorSource, diff --git a/packages/kbn-mapbox-gl/tsconfig.json b/packages/kbn-mapbox-gl/tsconfig.json index 159f40c6a7ca69..e935276e917623 100644 --- a/packages/kbn-mapbox-gl/tsconfig.json +++ b/packages/kbn-mapbox-gl/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig.bazel.json", "compilerOptions": { - "outDir": "./target/types", "declaration": true, "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-mapbox-gl/src", From c2f310006df2aff38db5bd8608bc76b55a73c4b4 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 18 Aug 2021 14:53:19 -0500 Subject: [PATCH 20/36] [build] Remove optimize dir creation (#109014) --- src/dev/build/tasks/create_empty_dirs_and_files_task.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dev/build/tasks/create_empty_dirs_and_files_task.ts b/src/dev/build/tasks/create_empty_dirs_and_files_task.ts index 06b402c5801512..6ef878cbab554a 100644 --- a/src/dev/build/tasks/create_empty_dirs_and_files_task.ts +++ b/src/dev/build/tasks/create_empty_dirs_and_files_task.ts @@ -12,9 +12,6 @@ export const CreateEmptyDirsAndFiles: Task = { description: 'Creating some empty directories and files to prevent file-permission issues', async run(config, log, build) { - await Promise.all([ - mkdirp(build.resolvePath('plugins')), - mkdirp(build.resolvePath('data/optimize')), - ]); + await mkdirp(build.resolvePath('plugins')); }, }; From 8c8dca6ad2b3c6a4017b98880cb80f10443889d3 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 18 Aug 2021 22:29:24 +0200 Subject: [PATCH 21/36] [RAC] [Observability] Use simpler alert severity level mapping (#109068) * [RAC][Observability] remove severity fields from mapping keep only ALERT_SEVERITY * temporarily remove severity value occurences * remove ALERT_SEVERITY_VALUE occurences, this value is not being read and shown in the Observability alerts table * remove duplicate ALERT_SEVERITY identifier Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../src/technical_field_names.ts | 6 ------ .../components/alerting/register_apm_alerts.ts | 8 ++++---- .../charts/helper/get_alert_annotations.test.tsx | 6 +++--- .../charts/helper/get_alert_annotations.tsx | 8 ++++---- .../latency_chart/latency_chart.stories.tsx | 6 +++--- ...ter_transaction_duration_anomaly_alert_type.ts | 12 ++++-------- .../public/pages/alerts/example_data.ts | 15 +++------------ .../public/pages/alerts/render_cell_value.tsx | 8 ++++---- .../assets/field_maps/technical_rule_field_map.ts | 3 +-- .../server/lib/alerts/duration_anomaly.test.ts | 6 ++---- .../uptime/server/lib/alerts/duration_anomaly.ts | 6 ++---- .../uptime/server/lib/alerts/status_check.test.ts | 6 +++--- .../uptime/server/lib/alerts/status_check.ts | 4 ++-- .../plugins/uptime/server/lib/alerts/tls.test.ts | 4 ++-- x-pack/plugins/uptime/server/lib/alerts/tls.ts | 4 ++-- 15 files changed, 39 insertions(+), 63 deletions(-) diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index 2aa23195df8997..fa3d61d00529cf 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -32,8 +32,6 @@ const ALERT_ID = `${ALERT_NAMESPACE}.id` as const; const ALERT_REASON = `${ALERT_NAMESPACE}.reason` as const; const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const; const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const; -const ALERT_SEVERITY_LEVEL = `${ALERT_NAMESPACE}.severity.level` as const; -const ALERT_SEVERITY_VALUE = `${ALERT_NAMESPACE}.severity.value` as const; const ALERT_START = `${ALERT_NAMESPACE}.start` as const; const ALERT_STATUS = `${ALERT_NAMESPACE}.status` as const; const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const; @@ -127,8 +125,6 @@ const fields = { ALERT_RULE_VERSION, ALERT_START, ALERT_SEVERITY, - ALERT_SEVERITY_LEVEL, - ALERT_SEVERITY_VALUE, ALERT_STATUS, ALERT_SYSTEM_STATUS, ALERT_UUID, @@ -183,8 +179,6 @@ export { ALERT_RULE_VERSION, ALERT_RULE_SEVERITY, ALERT_SEVERITY, - ALERT_SEVERITY_LEVEL, - ALERT_SEVERITY_VALUE, ALERT_START, ALERT_SYSTEM_STATUS, ALERT_UUID, diff --git a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts index 5905f700b0bbe3..dc52d572e2f35f 100644 --- a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts +++ b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts @@ -11,12 +11,12 @@ import { stringify } from 'querystring'; import type { ALERT_EVALUATION_THRESHOLD as ALERT_EVALUATION_THRESHOLD_TYPED, ALERT_EVALUATION_VALUE as ALERT_EVALUATION_VALUE_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_TYPED, } from '@kbn/rule-data-utils'; import { ALERT_EVALUATION_THRESHOLD as ALERT_EVALUATION_THRESHOLD_NON_TYPED, ALERT_EVALUATION_VALUE as ALERT_EVALUATION_VALUE_NON_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_NON_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_NON_TYPED, // @ts-expect-error } from '@kbn/rule-data-utils/target_node/technical_field_names'; import type { ObservabilityRuleTypeRegistry } from '../../../../observability/public'; @@ -36,7 +36,7 @@ const TRANSACTION_TYPE = 'transaction.type'; const ALERT_EVALUATION_THRESHOLD: typeof ALERT_EVALUATION_THRESHOLD_TYPED = ALERT_EVALUATION_THRESHOLD_NON_TYPED; const ALERT_EVALUATION_VALUE: typeof ALERT_EVALUATION_VALUE_TYPED = ALERT_EVALUATION_VALUE_NON_TYPED; -const ALERT_SEVERITY_LEVEL: typeof ALERT_SEVERITY_LEVEL_TYPED = ALERT_SEVERITY_LEVEL_NON_TYPED; +const ALERT_SEVERITY: typeof ALERT_SEVERITY_TYPED = ALERT_SEVERITY_NON_TYPED; const format = ({ pathname, @@ -211,7 +211,7 @@ export function registerApmAlerts( format: ({ fields }) => ({ reason: formatTransactionDurationAnomalyReason({ serviceName: String(fields[SERVICE_NAME][0]), - severityLevel: String(fields[ALERT_SEVERITY_LEVEL]), + severityLevel: String(fields[ALERT_SEVERITY]), measured: Number(fields[ALERT_EVALUATION_VALUE]), }), link: format({ diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx index 0f09b042a587bf..25a37570182bf7 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx @@ -13,7 +13,7 @@ import { ALERT_ID, ALERT_RULE_PRODUCER, ALERT_RULE_CONSUMER, - ALERT_SEVERITY_LEVEL, + ALERT_SEVERITY, ALERT_START, ALERT_STATUS, ALERT_UUID, @@ -163,7 +163,7 @@ describe('getAlertAnnotations', () => { describe('with an alert with a warning severity', () => { const warningAlert: Alert = { ...alert, - [ALERT_SEVERITY_LEVEL]: ['warning'], + [ALERT_SEVERITY]: ['warning'], }; it('uses the warning color', () => { @@ -196,7 +196,7 @@ describe('getAlertAnnotations', () => { describe('with an alert with a critical severity', () => { const criticalAlert: Alert = { ...alert, - [ALERT_SEVERITY_LEVEL]: ['critical'], + [ALERT_SEVERITY]: ['critical'], }; it('uses the critical color', () => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx index f51494b8fa1d80..4aef5f6e56b96a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx @@ -14,7 +14,7 @@ import { EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { ALERT_DURATION as ALERT_DURATION_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_TYPED, ALERT_START as ALERT_START_TYPED, ALERT_UUID as ALERT_UUID_TYPED, ALERT_RULE_TYPE_ID as ALERT_RULE_TYPE_ID_TYPED, @@ -22,7 +22,7 @@ import type { } from '@kbn/rule-data-utils'; import { ALERT_DURATION as ALERT_DURATION_NON_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_NON_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_NON_TYPED, ALERT_START as ALERT_START_NON_TYPED, ALERT_UUID as ALERT_UUID_NON_TYPED, ALERT_RULE_TYPE_ID as ALERT_RULE_TYPE_ID_NON_TYPED, @@ -38,7 +38,7 @@ import { asDuration, asPercent } from '../../../../../common/utils/formatters'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; const ALERT_DURATION: typeof ALERT_DURATION_TYPED = ALERT_DURATION_NON_TYPED; -const ALERT_SEVERITY_LEVEL: typeof ALERT_SEVERITY_LEVEL_TYPED = ALERT_SEVERITY_LEVEL_NON_TYPED; +const ALERT_SEVERITY: typeof ALERT_SEVERITY_TYPED = ALERT_SEVERITY_NON_TYPED; const ALERT_START: typeof ALERT_START_TYPED = ALERT_START_NON_TYPED; const ALERT_UUID: typeof ALERT_UUID_TYPED = ALERT_UUID_NON_TYPED; const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED = ALERT_RULE_TYPE_ID_NON_TYPED; @@ -119,7 +119,7 @@ export function getAlertAnnotations({ new Date(parsed[ALERT_START]!).getTime() ); const end = start + parsed[ALERT_DURATION]! / 1000; - const severityLevel = parsed[ALERT_SEVERITY_LEVEL]; + const severityLevel = parsed[ALERT_SEVERITY]; const color = getAlertColor({ severityLevel, theme }); const header = getAlertHeader({ severityLevel }); const formatter = getFormatter(parsed[ALERT_RULE_TYPE_ID]!); diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx index f9b22c422e3e3d..17fdef952658d8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx @@ -11,7 +11,7 @@ import { ALERT_RULE_TYPE_ID, ALERT_EVALUATION_VALUE, ALERT_ID, - ALERT_SEVERITY_LEVEL, + ALERT_SEVERITY, ALERT_START, ALERT_STATUS, ALERT_UUID, @@ -158,7 +158,7 @@ Example.args = { tags: ['apm', 'service.name:frontend-rum'], 'transaction.type': ['page-load'], [ALERT_RULE_PRODUCER]: ['apm'], - [ALERT_SEVERITY_LEVEL]: ['warning'], + [ALERT_SEVERITY]: ['warning'], [ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478181'], [ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'], 'event.action': ['active'], @@ -180,7 +180,7 @@ Example.args = { tags: ['apm', 'service.name:frontend-rum'], 'transaction.type': ['page-load'], [ALERT_RULE_PRODUCER]: ['apm'], - [ALERT_SEVERITY_LEVEL]: ['critical'], + [ALERT_SEVERITY]: ['critical'], [ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478182'], [ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'], 'event.action': ['active'], diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts index e38262773b6db7..7d49833c01abf3 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts @@ -12,15 +12,13 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import type { ALERT_EVALUATION_THRESHOLD as ALERT_EVALUATION_THRESHOLD_TYPED, ALERT_EVALUATION_VALUE as ALERT_EVALUATION_VALUE_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_TYPED, - ALERT_SEVERITY_VALUE as ALERT_SEVERITY_VALUE_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_TYPED, ALERT_REASON as ALERT_REASON_TYPED, } from '@kbn/rule-data-utils'; import { ALERT_EVALUATION_THRESHOLD as ALERT_EVALUATION_THRESHOLD_NON_TYPED, ALERT_EVALUATION_VALUE as ALERT_EVALUATION_VALUE_NON_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_NON_TYPED, - ALERT_SEVERITY_VALUE as ALERT_SEVERITY_VALUE_NON_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_NON_TYPED, ALERT_REASON as ALERT_REASON_NON_TYPED, // @ts-expect-error } from '@kbn/rule-data-utils/target_node/technical_field_names'; @@ -51,8 +49,7 @@ import { const ALERT_EVALUATION_THRESHOLD: typeof ALERT_EVALUATION_THRESHOLD_TYPED = ALERT_EVALUATION_THRESHOLD_NON_TYPED; const ALERT_EVALUATION_VALUE: typeof ALERT_EVALUATION_VALUE_TYPED = ALERT_EVALUATION_VALUE_NON_TYPED; -const ALERT_SEVERITY_LEVEL: typeof ALERT_SEVERITY_LEVEL_TYPED = ALERT_SEVERITY_LEVEL_NON_TYPED; -const ALERT_SEVERITY_VALUE: typeof ALERT_SEVERITY_VALUE_TYPED = ALERT_SEVERITY_VALUE_NON_TYPED; +const ALERT_SEVERITY: typeof ALERT_SEVERITY_TYPED = ALERT_SEVERITY_NON_TYPED; const ALERT_REASON: typeof ALERT_REASON_TYPED = ALERT_REASON_NON_TYPED; const paramsSchema = schema.object({ @@ -258,8 +255,7 @@ export function registerTransactionDurationAnomalyAlertType({ ...getEnvironmentEsField(environment), [TRANSACTION_TYPE]: transactionType, [PROCESSOR_EVENT]: ProcessorEvent.transaction, - [ALERT_SEVERITY_LEVEL]: severityLevel, - [ALERT_SEVERITY_VALUE]: score, + [ALERT_SEVERITY]: severityLevel, [ALERT_EVALUATION_VALUE]: score, [ALERT_EVALUATION_THRESHOLD]: threshold, [ALERT_REASON]: formatTransactionDurationAnomalyReason({ diff --git a/x-pack/plugins/observability/public/pages/alerts/example_data.ts b/x-pack/plugins/observability/public/pages/alerts/example_data.ts index 112932d49311c4..535556a9b6ec1b 100644 --- a/x-pack/plugins/observability/public/pages/alerts/example_data.ts +++ b/x-pack/plugins/observability/public/pages/alerts/example_data.ts @@ -9,8 +9,7 @@ import { ALERT_DURATION, ALERT_END, ALERT_ID, - ALERT_SEVERITY_LEVEL, - ALERT_SEVERITY_VALUE, + ALERT_SEVERITY, ALERT_RULE_TYPE_ID, ALERT_START, ALERT_STATUS, @@ -28,7 +27,7 @@ export const apmAlertResponseExample = [ [ALERT_RULE_NAME]: ['Error count threshold | opbeans-java (smith test)'], [ALERT_DURATION]: [180057000], [ALERT_STATUS]: ['open'], - [ALERT_SEVERITY_LEVEL]: ['warning'], + [ALERT_SEVERITY]: ['warning'], tags: ['apm', 'service.name:opbeans-java'], [ALERT_UUID]: ['0175ec0a-a3b1-4d41-b557-e21c2d024352'], [ALERT_RULE_UUID]: ['474920d0-93e9-11eb-ac86-0b455460de81'], @@ -123,21 +122,13 @@ export const dynamicIndexPattern = { readFromDocValues: true, }, { - name: ALERT_SEVERITY_LEVEL, + name: ALERT_SEVERITY, type: 'string', esTypes: ['keyword'], searchable: true, aggregatable: true, readFromDocValues: true, }, - { - name: ALERT_SEVERITY_VALUE, - type: 'number', - esTypes: ['long'], - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, { name: ALERT_START, type: 'date', diff --git a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx b/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx index 34b595ddc34f3b..c85ea0b1086fab 100644 --- a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx @@ -14,13 +14,13 @@ import React, { useEffect } from 'react'; */ import type { ALERT_DURATION as ALERT_DURATION_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_TYPED, ALERT_STATUS as ALERT_STATUS_TYPED, ALERT_REASON as ALERT_REASON_TYPED, } from '@kbn/rule-data-utils'; import { ALERT_DURATION as ALERT_DURATION_NON_TYPED, - ALERT_SEVERITY_LEVEL as ALERT_SEVERITY_LEVEL_NON_TYPED, + ALERT_SEVERITY as ALERT_SEVERITY_NON_TYPED, ALERT_STATUS as ALERT_STATUS_NON_TYPED, ALERT_REASON as ALERT_REASON_NON_TYPED, TIMESTAMP, @@ -37,7 +37,7 @@ import { usePluginContext } from '../../hooks/use_plugin_context'; import { useTheme } from '../../hooks/use_theme'; const ALERT_DURATION: typeof ALERT_DURATION_TYPED = ALERT_DURATION_NON_TYPED; -const ALERT_SEVERITY_LEVEL: typeof ALERT_SEVERITY_LEVEL_TYPED = ALERT_SEVERITY_LEVEL_NON_TYPED; +const ALERT_SEVERITY: typeof ALERT_SEVERITY_TYPED = ALERT_SEVERITY_NON_TYPED; const ALERT_STATUS: typeof ALERT_STATUS_TYPED = ALERT_STATUS_NON_TYPED; const ALERT_REASON: typeof ALERT_REASON_TYPED = ALERT_REASON_NON_TYPED; @@ -118,7 +118,7 @@ export const getRenderCellValue = ({ return ; case ALERT_DURATION: return asDuration(Number(value)); - case ALERT_SEVERITY_LEVEL: + case ALERT_SEVERITY: return ; case ALERT_REASON: const dataFieldEs = data.reduce((acc, d) => ({ ...acc, [d.field]: d.value }), {}); diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index f6566ee75920f9..b4ae89b7694f72 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -25,8 +25,7 @@ export const technicalRuleFieldMap = { [Fields.ALERT_START]: { type: 'date' }, [Fields.ALERT_END]: { type: 'date' }, [Fields.ALERT_DURATION]: { type: 'long' }, - [Fields.ALERT_SEVERITY_LEVEL]: { type: 'keyword' }, - [Fields.ALERT_SEVERITY_VALUE]: { type: 'long' }, + [Fields.ALERT_SEVERITY]: { type: 'keyword' }, [Fields.ALERT_STATUS]: { type: 'keyword' }, [Fields.ALERT_EVALUATION_THRESHOLD]: { type: 'scaled_float', scaling_factor: 100 }, [Fields.ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100 }, diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.test.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.test.ts index d69453fd76b91b..b87fbc2c45ac7a 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.test.ts @@ -5,8 +5,7 @@ * 2.0. */ import { - ALERT_SEVERITY_LEVEL, - ALERT_SEVERITY_VALUE, + ALERT_SEVERITY, ALERT_EVALUATION_VALUE, ALERT_EVALUATION_THRESHOLD, ALERT_REASON, @@ -171,8 +170,7 @@ describe('duration anomaly alert', () => { 'observer.geo.name': anomaly.entityValue, [ALERT_EVALUATION_VALUE]: anomaly.actualSort, [ALERT_EVALUATION_THRESHOLD]: anomaly.typicalSort, - [ALERT_SEVERITY_LEVEL]: getSeverityType(anomaly.severity), - [ALERT_SEVERITY_VALUE]: anomaly.severity, + [ALERT_SEVERITY]: getSeverityType(anomaly.severity), [ALERT_REASON]: `Abnormal (${getSeverityType( anomaly.severity )} level) response time detected on uptime-monitor with url ${ diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts index 3da0fcf65cbe41..cf241386ec2771 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts @@ -8,8 +8,7 @@ import { KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; import moment from 'moment'; import { schema } from '@kbn/config-schema'; import { - ALERT_SEVERITY_LEVEL, - ALERT_SEVERITY_VALUE, + ALERT_SEVERITY, ALERT_EVALUATION_VALUE, ALERT_EVALUATION_THRESHOLD, ALERT_REASON, @@ -135,8 +134,7 @@ export const durationAnomalyAlertFactory: UptimeAlertTypeFactory 'anomaly.bucket_span.minutes': summary.bucketSpan, [ALERT_EVALUATION_VALUE]: anomaly.actualSort, [ALERT_EVALUATION_THRESHOLD]: anomaly.typicalSort, - [ALERT_SEVERITY_LEVEL]: summary.severity, - [ALERT_SEVERITY_VALUE]: summary.severityScore, + [ALERT_SEVERITY]: summary.severity, [ALERT_REASON]: generateAlertMessage( CommonDurationAnomalyTranslations.defaultActionMessage, summary diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts index 4cf7a566454c48..4afe7e829d058b 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ALERT_REASON, ALERT_SEVERITY_WARNING, ALERT_SEVERITY_LEVEL } from '@kbn/rule-data-utils'; +import { ALERT_REASON, ALERT_SEVERITY_WARNING, ALERT_SEVERITY } from '@kbn/rule-data-utils'; import { generateFilterDSL, hasFilters, @@ -75,7 +75,7 @@ const mockStatusAlertDocument = ( [ALERT_REASON]: `Monitor first with url ${monitorInfo?.url?.full} is down from ${ monitorInfo.observer?.geo?.name }. The latest error message is ${monitorInfo.error?.message || ''}`, - [ALERT_SEVERITY_LEVEL]: ALERT_SEVERITY_WARNING, + [ALERT_SEVERITY]: ALERT_SEVERITY_WARNING, }, id: getInstanceId( monitorInfo, @@ -96,7 +96,7 @@ const mockAvailabilityAlertDocument = (monitor: GetMonitorAvailabilityResult) => )}% availability expected is 99.34% from ${ monitorInfo.observer?.geo?.name }. The latest error message is ${monitorInfo.error?.message || ''}`, - [ALERT_SEVERITY_LEVEL]: ALERT_SEVERITY_WARNING, + [ALERT_SEVERITY]: ALERT_SEVERITY_WARNING, }, id: getInstanceId(monitorInfo, `${monitorInfo?.monitor.id}-${monitorInfo.observer?.geo?.name}`), }; diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 4b00b7316b6878..55de5069aada93 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -7,7 +7,7 @@ import { min } from 'lodash'; import datemath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; -import { ALERT_SEVERITY_WARNING, ALERT_SEVERITY_LEVEL } from '@kbn/rule-data-utils'; +import { ALERT_SEVERITY_WARNING, ALERT_SEVERITY } from '@kbn/rule-data-utils'; import { i18n } from '@kbn/i18n'; import { JsonObject } from '@kbn/utility-types'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; @@ -159,7 +159,7 @@ export const getMonitorAlertDocument = (monitorSummary: Record { 'tls.server.x509.not_after': cert.not_after, 'tls.server.x509.not_before': cert.not_before, 'tls.server.hash.sha256': cert.sha256, - [ALERT_SEVERITY_LEVEL]: ALERT_SEVERITY_WARNING, + [ALERT_SEVERITY]: ALERT_SEVERITY_WARNING, }), id: `${cert.common_name}-${cert.issuer?.replace(/\s/g, '_')}-${cert.sha256}`, }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index 88fa88b24d22e6..63636ddfe59060 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; import { schema } from '@kbn/config-schema'; -import { ALERT_REASON, ALERT_SEVERITY_WARNING, ALERT_SEVERITY_LEVEL } from '@kbn/rule-data-utils'; +import { ALERT_REASON, ALERT_SEVERITY_WARNING, ALERT_SEVERITY } from '@kbn/rule-data-utils'; import { UptimeAlertTypeFactory } from './types'; import { updateState, generateAlertMessage } from './common'; import { TLS } from '../../../common/constants/alerts'; @@ -172,7 +172,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, 'tls.server.x509.not_after': cert.not_after, 'tls.server.x509.not_before': cert.not_before, 'tls.server.hash.sha256': cert.sha256, - [ALERT_SEVERITY_LEVEL]: ALERT_SEVERITY_WARNING, + [ALERT_SEVERITY]: ALERT_SEVERITY_WARNING, [ALERT_REASON]: generateAlertMessage(TlsTranslations.defaultActionMessage, summary), }, }); From 49ba7e746c9cb8123499d62bbcfa109a2d116bb8 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Wed, 18 Aug 2021 14:46:17 -0600 Subject: [PATCH 22/36] Add deprecation notice to dashboard import/export docs. (#108826) --- docs/api/dashboard-api.asciidoc | 2 ++ docs/api/dashboard/export-dashboard.asciidoc | 4 +++- docs/api/dashboard/import-dashboard.asciidoc | 4 +++- docs/user/dashboard/dashboard.asciidoc | 2 +- src/plugins/legacy_export/README.md | 2 +- src/plugins/legacy_export/server/plugin.ts | 1 + 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/api/dashboard-api.asciidoc b/docs/api/dashboard-api.asciidoc index 50c2abc9757634..e6f54dd9156ecb 100644 --- a/docs/api/dashboard-api.asciidoc +++ b/docs/api/dashboard-api.asciidoc @@ -1,6 +1,8 @@ [[dashboard-api]] == Import and export dashboard APIs +deprecated::[7.15.0,Both of these APIs have been deprecated in favor of <> and <>.] + Import and export dashboards with the corresponding saved objects, such as visualizations, saved searches, and index patterns. diff --git a/docs/api/dashboard/export-dashboard.asciidoc b/docs/api/dashboard/export-dashboard.asciidoc index 6d239d755eb0d0..3a20eff0a54d25 100644 --- a/docs/api/dashboard/export-dashboard.asciidoc +++ b/docs/api/dashboard/export-dashboard.asciidoc @@ -4,7 +4,9 @@ Export dashboard ++++ -experimental[] Export dashboards and corresponding saved objects. +deprecated::[7.15.0,Use <> instead.] + +Export dashboards and corresponding saved objects. [[dashboard-api-export-request]] ==== Request diff --git a/docs/api/dashboard/import-dashboard.asciidoc b/docs/api/dashboard/import-dashboard.asciidoc index 5d1fab41a2a140..e4817d6cb7ee9b 100644 --- a/docs/api/dashboard/import-dashboard.asciidoc +++ b/docs/api/dashboard/import-dashboard.asciidoc @@ -4,7 +4,9 @@ Import dashboard ++++ -experimental[] Import dashboards and corresponding saved objects. +deprecated::[7.15.0,Use <> instead.] + +Import dashboards and corresponding saved objects. [[dashboard-api-import-request]] ==== Request diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index 374dc4f735e9be..6430c5d246dc6e 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -345,7 +345,7 @@ To share the dashboard with a larger audience, click *Share* in the toolbar. For [[import-dashboards]] == Export dashboards -To automate {kib}, you can export dashboards as JSON using the <>. It is important to export dashboards with all necessary references. +To automate {kib}, you can export dashboards as NDJSON using the <>. It is important to export dashboards with all necessary references. -- include::tutorial-create-a-dashboard-of-lens-panels.asciidoc[] diff --git a/src/plugins/legacy_export/README.md b/src/plugins/legacy_export/README.md index 050e39b8f19e4a..551487a1122fcd 100644 --- a/src/plugins/legacy_export/README.md +++ b/src/plugins/legacy_export/README.md @@ -1,3 +1,3 @@ -# `legacyExport` plugin +# `legacyExport` plugin [deprecated] The `legacyExport` plugin adds support for the legacy saved objects export format. diff --git a/src/plugins/legacy_export/server/plugin.ts b/src/plugins/legacy_export/server/plugin.ts index ac38f300bd02b5..a6bdcdc19b0a1b 100644 --- a/src/plugins/legacy_export/server/plugin.ts +++ b/src/plugins/legacy_export/server/plugin.ts @@ -9,6 +9,7 @@ import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; import { registerRoutes } from './routes'; +/** @deprecated */ export class LegacyExportPlugin implements Plugin<{}, {}> { constructor(private readonly initContext: PluginInitializerContext) {} From 83a772019647c255817a786d94a0351efff1aba9 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Wed, 18 Aug 2021 16:56:36 -0500 Subject: [PATCH 23/36] [Workplace Search] Fix rendering bug on Private Source status (#109122) * Fix rendering bug on Private Source status * Fix rendering bug on Private Source status * Refactor per code review --- .../components/source_info_card.test.tsx | 2 +- .../content_sources/components/source_info_card.tsx | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx index 7c4c02cdc9819d..c779d76af5e750 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.test.tsx @@ -29,7 +29,7 @@ describe('SourceInfoCard', () => { expect(wrapper.find(SourceIcon)).toHaveLength(1); expect(wrapper.find(EuiBadge)).toHaveLength(1); expect(wrapper.find(EuiHealth)).toHaveLength(1); - expect(wrapper.find(EuiText)).toHaveLength(3); + expect(wrapper.find(EuiText)).toHaveLength(1); expect(wrapper.find(EuiTitle)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx index d98b4f6b1e67d8..e2c9cc05b04c14 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx @@ -64,16 +64,12 @@ export const SourceInfoCard: React.FC = ({ {isFederatedSource && ( - + - - {STATUS_LABEL} - + {STATUS_LABEL} - - {READY_TEXT} - + {READY_TEXT} )} From 447ae98bc8236720be14ddbe30244d42095659e0 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Wed, 18 Aug 2021 18:20:31 -0400 Subject: [PATCH 24/36] [Security Solution][Endpoint][Event Filters] Event filters updated UI (#109039) --- .../exception_item/exception_details.tsx | 47 ++++++++++++++----- .../exception_item/exception_entries.tsx | 21 +++++++-- .../viewer/exception_item/index.tsx | 2 +- .../view/components/empty/index.tsx | 2 +- .../view/event_filters_list_page.tsx | 10 ++-- .../pages/event_filters/view/translations.ts | 3 +- 6 files changed, 60 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx index 2bb6854739a324..3354637b9f7458 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx @@ -12,6 +12,7 @@ import { EuiButtonEmpty, EuiDescriptionListTitle, EuiDescriptionListDescription, + EuiToolTip, } from '@elastic/eui'; import React, { useMemo, Fragment } from 'react'; import styled, { css } from 'styled-components'; @@ -25,15 +26,28 @@ const MyExceptionDetails = styled(EuiFlexItem)` ${({ theme }) => css` background-color: ${theme.eui.euiColorLightestShade}; padding: ${theme.eui.euiSize}; + .eventFiltersDescriptionList { + margin: ${theme.eui.euiSize} ${theme.eui.euiSize} 0 ${theme.eui.euiSize}; + } + .eventFiltersDescriptionListTitle { + width: 40%; + margin-top: 0; + margin-bottom: ${theme.eui.euiSizeS}; + } + .eventFiltersDescriptionListDescription { + width: 60%; + margin-top: 0; + margin-bottom: ${theme.eui.euiSizeS}; + } `} `; -const MyDescriptionListTitle = styled(EuiDescriptionListTitle)` - width: 40%; -`; - -const MyDescriptionListDescription = styled(EuiDescriptionListDescription)` - width: 60%; +const StyledCommentsSection = styled(EuiFlexItem)` + ${({ theme }) => css` + &&& { + margin: ${theme.eui.euiSizeXS} ${theme.eui.euiSize} ${theme.eui.euiSizeL} ${theme.eui.euiSize}; + } + `} `; const ExceptionDetailsComponent = ({ @@ -77,19 +91,28 @@ const ExceptionDetailsComponent = ({ return ( - + {descriptionListItems.map((item) => ( - {item.title} - - {item.description} - + + + {item.title} + + + + + {item.description} + + ))} - {commentsSection} + {commentsSection} ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx index 7429a934d557d7..18b7298136302b 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx @@ -15,6 +15,7 @@ import { EuiHideFor, EuiBadge, EuiBadgeGroup, + EuiToolTip, } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled, { css } from 'styled-components'; @@ -26,7 +27,12 @@ import * as i18n from '../../translations'; import { FormattedEntry } from '../../types'; const MyEntriesDetails = styled(EuiFlexItem)` - padding: ${({ theme }) => theme.eui.euiSize}; + ${({ theme }) => css` + padding: ${theme.eui.euiSize} ${theme.eui.euiSizeL} ${theme.eui.euiSizeL} ${theme.eui.euiSizeXS}; + &&& { + margin-left: 0; + } + `} `; const MyEditButton = styled(EuiButton)` @@ -46,8 +52,9 @@ const MyRemoveButton = styled(EuiButton)` `; const MyAndOrBadgeContainer = styled(EuiFlexItem)` - padding-top: ${({ theme }) => theme.eui.euiSizeXL}; - padding-bottom: ${({ theme }) => theme.eui.euiSizeS}; + ${({ theme }) => css` + padding: ${theme.eui.euiSizeXL} ${theme.eui.euiSize} ${theme.eui.euiSizeS} 0; + `} `; const MyActionButton = styled(EuiFlexItem)` @@ -132,7 +139,13 @@ const ExceptionEntriesComponent = ({ ); } else { - return values ?? getEmptyValue(); + return values ? ( + + {values} + + ) : ( + getEmptyValue() + ); } }, }, diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx index b73442b04c9b4d..6a53f47baf6b85 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx @@ -84,7 +84,7 @@ const ExceptionItemComponent = ({ }, [loadingItemIds, exceptionItem.id]); return ( - + diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/empty/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/empty/index.tsx index 9ad2549c85642c..e27adc851dab7e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/empty/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/empty/index.tsx @@ -48,7 +48,7 @@ export const EventFiltersListEmptyState = memo<{ > } diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx index 8466e19100f730..e206f85df65481 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx @@ -11,7 +11,7 @@ import { Dispatch } from 'redux'; import { useHistory, useLocation } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiSpacer, EuiHorizontalRule, EuiText } from '@elastic/eui'; +import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; @@ -192,7 +192,7 @@ export const EventFiltersListPage = memo(() => { title={ } subtitle={ABOUT_EVENT_FILTERS} @@ -207,7 +207,7 @@ export const EventFiltersListPage = memo(() => { > ) @@ -236,11 +236,11 @@ export const EventFiltersListPage = memo(() => { - + )} diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts index 4c127ee47003f9..ae8012711fbf15 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts @@ -54,6 +54,5 @@ export const getGetErrorMessage = (getError: ServerApiError) => { export const ABOUT_EVENT_FILTERS = i18n.translate('xpack.securitySolution.eventFilters.aboutInfo', { defaultMessage: - 'Add an event filter to exclude high volume or unwanted events from being written to Elasticsearch. Event ' + - 'filters are processed by the Endpoint Security integration, and are applied to hosts running this integration on their agents.', + 'Add an event filter to exclude high volume or unwanted events from being written to Elasticsearch.', }); From f719aa40319d316616069ba67c4ad921eeccc531 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 18 Aug 2021 19:02:11 -0400 Subject: [PATCH 25/36] [Fleet] Missing migration backport in 7.14.0 (#109174) --- x-pack/plugins/fleet/server/saved_objects/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 54cb0846207a30..449a1984aa53b3 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -322,6 +322,7 @@ const getSavedObjectTypes = ( }, migrations: { '7.14.0': migrateInstallationToV7140, + '7.14.1': migrateInstallationToV7140, }, }, [ASSETS_SAVED_OBJECT_TYPE]: { From 64454f32839c692849ebd72f37151ae73b5f3d6b Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 18 Aug 2021 20:05:16 -0500 Subject: [PATCH 26/36] Revert "[build] Remove optimize dir creation (#109014)" This reverts commit c2f310006df2aff38db5bd8608bc76b55a73c4b4. --- src/dev/build/tasks/create_empty_dirs_and_files_task.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dev/build/tasks/create_empty_dirs_and_files_task.ts b/src/dev/build/tasks/create_empty_dirs_and_files_task.ts index 6ef878cbab554a..06b402c5801512 100644 --- a/src/dev/build/tasks/create_empty_dirs_and_files_task.ts +++ b/src/dev/build/tasks/create_empty_dirs_and_files_task.ts @@ -12,6 +12,9 @@ export const CreateEmptyDirsAndFiles: Task = { description: 'Creating some empty directories and files to prevent file-permission issues', async run(config, log, build) { - await mkdirp(build.resolvePath('plugins')); + await Promise.all([ + mkdirp(build.resolvePath('plugins')), + mkdirp(build.resolvePath('data/optimize')), + ]); }, }; From 09f122b4782c9555815d16580e699fb556be00a4 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Wed, 18 Aug 2021 21:25:32 -0500 Subject: [PATCH 27/36] Increase width of service inventory sparkplot columns (#109137) From 160 to 176px. Prevents overlapping of the labels. Fixes #109103. --- .../components/app/service_inventory/service_list/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx index 8a623300da81af..8732084e6331e1 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx @@ -163,7 +163,7 @@ export function getServiceColumns({ /> ), align: 'left', - width: showWhenSmallOrGreaterThanLarge ? `${unit * 10}px` : 'auto', + width: showWhenSmallOrGreaterThanLarge ? `${unit * 11}px` : 'auto', }, { field: 'throughput', @@ -184,7 +184,7 @@ export function getServiceColumns({ /> ), align: 'left', - width: showWhenSmallOrGreaterThanLarge ? `${unit * 10}px` : 'auto', + width: showWhenSmallOrGreaterThanLarge ? `${unit * 11}px` : 'auto', }, { field: 'transactionErrorRate', From 48ce73db15d813933b5295153231a6bc6ca41eb9 Mon Sep 17 00:00:00 2001 From: ymao1 Date: Wed, 18 Aug 2021 22:32:19 -0400 Subject: [PATCH 28/36] [Alerting] Update rules detail page to resolve SO IDs if necessary (#108091) * Adding internal resolve API to resolve rules given an ID * Updating after merge * Updating after merge * Adding resolveRule api to client and adding spacesOss plugin dependency * Handling 404 errors by calling resolve. Updating unit tests * Handling aliasMatch and conflict results * Fixing types * Unit tests for spaces oss components * Adding audit event for resolve Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/alerting/common/alert.ts | 8 +- .../plugins/alerting/server/routes/index.ts | 2 + .../server/routes/resolve_rule.test.ts | 182 +++++++ .../alerting/server/routes/resolve_rule.ts | 84 ++++ .../alerting/server/rules_client.mock.ts | 1 + .../server/rules_client/audit_events.ts | 3 + .../server/rules_client/rules_client.ts | 47 ++ .../server/rules_client/tests/resolve.test.ts | 451 ++++++++++++++++++ .../plugins/triggers_actions_ui/kibana.json | 2 +- .../public/application/app.tsx | 2 + .../lib/alert_api/common_transformations.ts | 15 +- .../public/application/lib/alert_api/index.ts | 1 + .../lib/alert_api/resolve_rule.test.ts | 104 ++++ .../application/lib/alert_api/resolve_rule.ts | 23 + .../components/alert_details_route.test.tsx | 336 +++++++++---- .../components/alert_details_route.tsx | 105 +++- .../with_bulk_alert_api_operations.test.tsx | 19 + .../with_bulk_alert_api_operations.tsx | 4 + .../common/lib/kibana/kibana_react.mock.ts | 3 +- .../triggers_actions_ui/public/plugin.ts | 3 + .../triggers_actions_ui/public/types.ts | 3 + .../plugins/triggers_actions_ui/tsconfig.json | 1 + 22 files changed, 1287 insertions(+), 112 deletions(-) create mode 100644 x-pack/plugins/alerting/server/routes/resolve_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/resolve_rule.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.test.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts diff --git a/x-pack/plugins/alerting/common/alert.ts b/x-pack/plugins/alerting/common/alert.ts index 64c759752faec5..1274e7b95b1141 100644 --- a/x-pack/plugins/alerting/common/alert.ts +++ b/x-pack/plugins/alerting/common/alert.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { SavedObjectAttribute, SavedObjectAttributes } from 'kibana/server'; +import { + SavedObjectAttribute, + SavedObjectAttributes, + SavedObjectsResolveResponse, +} from 'kibana/server'; import { AlertNotifyWhenType } from './alert_notify_when_type'; export type AlertTypeState = Record; @@ -76,6 +80,8 @@ export interface Alert { } export type SanitizedAlert = Omit, 'apiKey'>; +export type ResolvedSanitizedRule = SanitizedAlert & + Omit; export type SanitizedRuleConfig = Pick< SanitizedAlert, diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index ad1c97efe23343..c1c7eae45109ef 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -22,6 +22,7 @@ import { findRulesRoute } from './find_rules'; import { getRuleAlertSummaryRoute } from './get_rule_alert_summary'; import { getRuleStateRoute } from './get_rule_state'; import { healthRoute } from './health'; +import { resolveRuleRoute } from './resolve_rule'; import { ruleTypesRoute } from './rule_types'; import { muteAllRuleRoute } from './mute_all_rule'; import { muteAlertRoute } from './mute_alert'; @@ -42,6 +43,7 @@ export function defineRoutes(opts: RouteOptions) { defineLegacyRoutes(opts); createRuleRoute(opts); getRuleRoute(router, licenseState); + resolveRuleRoute(router, licenseState); updateRuleRoute(router, licenseState); deleteRuleRoute(router, licenseState); aggregateRulesRoute(router, licenseState); diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts new file mode 100644 index 00000000000000..b03369a74b8659 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts @@ -0,0 +1,182 @@ +/* + * 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 { pick } from 'lodash'; +import { resolveRuleRoute } from './resolve_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { rulesClientMock } from '../rules_client.mock'; +import { ResolvedSanitizedRule } from '../types'; +import { AsApiContract } from './lib'; + +const rulesClient = rulesClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('resolveRuleRoute', () => { + const mockedRule: ResolvedSanitizedRule<{ + bar: boolean; + }> = { + id: '1', + alertTypeId: '1', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date(), + updatedAt: new Date(), + actions: [ + { + group: 'default', + id: '2', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + consumer: 'bar', + name: 'abc', + tags: ['foo'], + enabled: true, + muteAll: false, + notifyWhen: 'onActionGroupChange', + createdBy: '', + updatedBy: '', + apiKeyOwner: '', + throttle: '30s', + mutedInstanceIds: [], + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }; + + const resolveResult: AsApiContract> = { + ...pick( + mockedRule, + 'consumer', + 'name', + 'schedule', + 'tags', + 'params', + 'throttle', + 'enabled', + 'alias_target_id' + ), + rule_type_id: mockedRule.alertTypeId, + notify_when: mockedRule.notifyWhen, + mute_all: mockedRule.muteAll, + created_by: mockedRule.createdBy, + updated_by: mockedRule.updatedBy, + api_key_owner: mockedRule.apiKeyOwner, + muted_alert_ids: mockedRule.mutedInstanceIds, + created_at: mockedRule.createdAt, + updated_at: mockedRule.updatedAt, + id: mockedRule.id, + execution_status: { + status: mockedRule.executionStatus.status, + last_execution_date: mockedRule.executionStatus.lastExecutionDate, + }, + actions: [ + { + group: mockedRule.actions[0].group, + id: mockedRule.actions[0].id, + params: mockedRule.actions[0].params, + connector_type_id: mockedRule.actions[0].actionTypeId, + }, + ], + outcome: 'aliasMatch', + }; + + it('resolves a rule with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + resolveRuleRoute(router, licenseState); + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_resolve"`); + + rulesClient.resolve.mockResolvedValueOnce(mockedRule); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + await handler(context, req, res); + + expect(rulesClient.resolve).toHaveBeenCalledTimes(1); + expect(rulesClient.resolve.mock.calls[0][0].id).toEqual('1'); + + expect(res.ok).toHaveBeenCalledWith({ + body: resolveResult, + }); + }); + + it('ensures the license allows resolving rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + resolveRuleRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + rulesClient.resolve.mockResolvedValueOnce(mockedRule); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents getting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + resolveRuleRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + rulesClient.resolve.mockResolvedValueOnce(mockedRule); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts new file mode 100644 index 00000000000000..011d28780e718c --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts @@ -0,0 +1,84 @@ +/* + * 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 { omit } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../lib'; +import { verifyAccessAndContext, RewriteResponseCase } from './lib'; +import { + AlertTypeParams, + AlertingRequestHandlerContext, + INTERNAL_BASE_ALERTING_API_PATH, + ResolvedSanitizedRule, +} from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const rewriteBodyRes: RewriteResponseCase> = ({ + alertTypeId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + executionStatus, + actions, + scheduledTaskId, + ...rest +}) => ({ + ...rest, + rule_type_id: alertTypeId, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, + api_key_owner: apiKeyOwner, + notify_when: notifyWhen, + mute_all: muteAll, + muted_alert_ids: mutedInstanceIds, + scheduled_task_id: scheduledTaskId, + execution_status: executionStatus && { + ...omit(executionStatus, 'lastExecutionDate'), + last_execution_date: executionStatus.lastExecutionDate, + }, + actions: actions.map(({ group, id, actionTypeId, params }) => ({ + group, + id, + params, + connector_type_id: actionTypeId, + })), +}); + +export const resolveRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_resolve`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const rulesClient = context.alerting.getRulesClient(); + const { id } = req.params; + const rule = await rulesClient.resolve({ id }); + return res.ok({ + body: rewriteBodyRes(rule), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 4bd197e51a5da1..438331a1cd5801 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -16,6 +16,7 @@ const createRulesClientMock = () => { aggregate: jest.fn(), create: jest.fn(), get: jest.fn(), + resolve: jest.fn(), getAlertState: jest.fn(), find: jest.fn(), delete: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/audit_events.ts b/x-pack/plugins/alerting/server/rules_client/audit_events.ts index f04b7c3701974b..5f6122458ddaf0 100644 --- a/x-pack/plugins/alerting/server/rules_client/audit_events.ts +++ b/x-pack/plugins/alerting/server/rules_client/audit_events.ts @@ -11,6 +11,7 @@ import { AuditEvent } from '../../../security/server'; export enum RuleAuditAction { CREATE = 'rule_create', GET = 'rule_get', + RESOLVE = 'rule_resolve', UPDATE = 'rule_update', UPDATE_API_KEY = 'rule_update_api_key', ENABLE = 'rule_enable', @@ -28,6 +29,7 @@ type VerbsTuple = [string, string, string]; const eventVerbs: Record = { rule_create: ['create', 'creating', 'created'], rule_get: ['access', 'accessing', 'accessed'], + rule_resolve: ['access', 'accessing', 'accessed'], rule_update: ['update', 'updating', 'updated'], rule_update_api_key: ['update API key of', 'updating API key of', 'updated API key of'], rule_enable: ['enable', 'enabling', 'enabled'], @@ -43,6 +45,7 @@ const eventVerbs: Record = { const eventTypes: Record = { rule_create: 'creation', rule_get: 'access', + rule_resolve: 'access', rule_update: 'change', rule_update_api_key: 'change', rule_enable: 'change', diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index a079a52448e2d5..4d191584592a2a 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -33,6 +33,7 @@ import { AlertExecutionStatusValues, AlertNotifyWhenType, AlertTypeParams, + ResolvedSanitizedRule, } from '../types'; import { validateAlertTypeParams, @@ -411,6 +412,52 @@ export class RulesClient { ); } + public async resolve({ + id, + }: { + id: string; + }): Promise> { + const { + saved_object: result, + ...resolveResponse + } = await this.unsecuredSavedObjectsClient.resolve('alert', id); + try { + await this.authorization.ensureAuthorized({ + ruleTypeId: result.attributes.alertTypeId, + consumer: result.attributes.consumer, + operation: ReadOperations.Get, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RESOLVE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RESOLVE, + savedObject: { type: 'alert', id }, + }) + ); + + const rule = this.getAlertFromRaw( + result.id, + result.attributes.alertTypeId, + result.attributes, + result.references + ); + + return { + ...rule, + ...resolveResponse, + }; + } + public async getAlertState({ id }: { id: string }): Promise { const alert = await this.get({ id }); await this.authorization.ensureAuthorized({ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts new file mode 100644 index 00000000000000..63feb4ff3147ac --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -0,0 +1,451 @@ +/* + * 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 { RulesClient, ConstructorOptions } from '../rules_client'; +import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { taskManagerMock } from '../../../../task_manager/server/mocks'; +import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; +import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; +import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; +import { ActionsAuthorization } from '../../../../actions/server'; +import { httpServerMock } from '../../../../../../src/core/server/mocks'; +import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { getBeforeSetup, setGlobalDate } from './lib'; +import { RecoveredActionGroup } from '../../../common'; + +const taskManager = taskManagerMock.createStart(); +const ruleTypeRegistry = ruleTypeRegistryMock.create(); +const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); +const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); +const authorization = alertingAuthorizationMock.create(); +const actionsAuthorization = actionsAuthorizationMock.create(); +const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); + +const kibanaVersion = 'v7.10.0'; +const rulesClientParams: jest.Mocked = { + taskManager, + ruleTypeRegistry, + unsecuredSavedObjectsClient, + authorization: (authorization as unknown) as AlertingAuthorization, + actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, + spaceId: 'default', + namespace: 'default', + getUserName: jest.fn(), + createAPIKey: jest.fn(), + logger: loggingSystemMock.create().get(), + encryptedSavedObjectsClient: encryptedSavedObjects, + getActionsClient: jest.fn(), + getEventLogClient: jest.fn(), + kibanaVersion, +}; + +beforeEach(() => { + getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); + (auditLogger.log as jest.Mock).mockClear(); +}); + +setGlobalDate(); + +describe('resolve()', () => { + test('calls saved objects client with given params', async () => { + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + const result = await rulesClient.resolve({ id: '1' }); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "alias_target_id": "2", + "createdAt": 2019-02-12T21:01:22.479Z, + "id": "1", + "notifyWhen": "onActiveAlert", + "outcome": "aliasMatch", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + expect(unsecuredSavedObjectsClient.resolve).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.resolve.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "alert", + "1", + ] + `); + }); + + test('should call useSavedObjectReferences.injectReferences if defined for rule type', async () => { + const injectReferencesFn = jest.fn().mockReturnValue({ + bar: true, + parameterThatIsSavedObjectId: '9', + }); + ruleTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: injectReferencesFn, + }, + })); + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'param:soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + const result = await rulesClient.resolve({ id: '1' }); + + expect(injectReferencesFn).toHaveBeenCalledWith( + { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + [{ id: '9', name: 'soRef_0', type: 'someSavedObjectType' }] + ); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "alias_target_id": "2", + "createdAt": 2019-02-12T21:01:22.479Z, + "id": "1", + "notifyWhen": "onActiveAlert", + "outcome": "aliasMatch", + "params": Object { + "bar": true, + "parameterThatIsSavedObjectId": "9", + }, + "schedule": Object { + "interval": "10s", + }, + "updatedAt": 2019-02-12T21:01:22.479Z, + } + `); + }); + + test(`throws an error when references aren't found`, async () => { + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + references: [], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + await expect(rulesClient.resolve({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Action reference \\"action_0\\" not found in alert id: 1"` + ); + }); + + test('throws an error if useSavedObjectReferences.injectReferences throws an error', async () => { + const injectReferencesFn = jest.fn().mockImplementation(() => { + throw new Error('something went wrong!'); + }); + ruleTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: injectReferencesFn, + }, + })); + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + parameterThatIsSavedObjectRef: 'soRef_0', + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'soRef_0', + type: 'someSavedObjectType', + id: '9', + }, + ], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + await expect(rulesClient.resolve({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Error injecting reference into rule params for rule id 1 - something went wrong!"` + ); + }); + + describe('authorization', () => { + beforeEach(() => { + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + consumer: 'myApp', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + }); + + test('ensures user is authorised to resolve this type of rule under the consumer', async () => { + const rulesClient = new RulesClient(rulesClientParams); + await rulesClient.resolve({ id: '1' }); + + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'get', + ruleTypeId: 'myType', + }); + }); + + test('throws when user is not authorised to get this type of alert', async () => { + const rulesClient = new RulesClient(rulesClientParams); + authorization.ensureAuthorized.mockRejectedValue( + new Error(`Unauthorized to get a "myType" alert for "myApp"`) + ); + + await expect(rulesClient.resolve({ id: '1' })).rejects.toMatchInlineSnapshot( + `[Error: Unauthorized to get a "myType" alert for "myApp"]` + ); + + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'get', + ruleTypeId: 'myType', + }); + }); + }); + + describe('auditLogger', () => { + beforeEach(() => { + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + actions: [], + }, + references: [], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + }); + + test('logs audit event when getting a rule', async () => { + const rulesClient = new RulesClient({ ...rulesClientParams, auditLogger }); + await rulesClient.resolve({ id: '1' }); + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action: 'rule_resolve', + outcome: 'success', + }), + kibana: { saved_object: { id: '1', type: 'alert' } }, + }) + ); + }); + + test('logs audit event when not authorised to get a rule', async () => { + const rulesClient = new RulesClient({ ...rulesClientParams, auditLogger }); + authorization.ensureAuthorized.mockRejectedValue(new Error('Unauthorized')); + + await expect(rulesClient.resolve({ id: '1' })).rejects.toThrow(); + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action: 'rule_resolve', + outcome: 'failure', + }), + kibana: { + saved_object: { + id: '1', + type: 'alert', + }, + }, + error: { + code: 'Error', + message: 'Unauthorized', + }, + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index 4033889d9811eb..9e9af347af2a28 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -8,7 +8,7 @@ "server": true, "ui": true, "optionalPlugins": ["alerting", "features", "home", "spaces"], - "requiredPlugins": ["management", "charts", "data", "kibanaReact", "kibanaUtils", "savedObjects"], + "requiredPlugins": ["management", "charts", "data", "kibanaReact", "kibanaUtils", "savedObjects", "spacesOss"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], "requiredBundles": ["home", "alerting", "esUiShared", "kibanaReact", "kibanaUtils"] diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 9786f5dcb949dc..cc34f1beaf6b98 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -18,6 +18,7 @@ import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { PluginStartContract as AlertingStart } from '../../../alerting/public'; import type { SpacesPluginStart } from '../../../spaces/public'; +import type { SpacesOssPluginStart } from '../../../../../src/plugins/spaces_oss/public'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; @@ -36,6 +37,7 @@ export interface TriggersAndActionsUiServices extends CoreStart { charts: ChartsPluginStart; alerting?: AlertingStart; spaces?: SpacesPluginStart; + spacesOss: SpacesOssPluginStart; storage?: Storage; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; actionTypeRegistry: ActionTypeRegistryContract; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/common_transformations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/common_transformations.ts index 749cf53cf740bc..5049a37c317dd6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/common_transformations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/common_transformations.ts @@ -6,7 +6,7 @@ */ import { AlertExecutionStatus } from '../../../../../alerting/common'; import { AsApiContract, RewriteRequestCase } from '../../../../../actions/common'; -import { Alert, AlertAction } from '../../../types'; +import { Alert, AlertAction, ResolvedRule } from '../../../types'; const transformAction: RewriteRequestCase = ({ group, @@ -59,3 +59,16 @@ export const transformAlert: RewriteRequestCase = ({ scheduledTaskId, ...rest, }); + +export const transformResolvedRule: RewriteRequestCase = ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + alias_target_id, + outcome, + ...rest +}: any) => { + return { + ...transformAlert(rest), + alias_target_id, + outcome, + }; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/index.ts index a0b090a474e283..c499f7955e2fe3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/index.ts @@ -22,3 +22,4 @@ export { loadAlertState } from './state'; export { unmuteAlertInstance } from './unmute_alert'; export { unmuteAlert, unmuteAlerts } from './unmute'; export { updateAlert } from './update'; +export { resolveRule } from './resolve_rule'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.test.ts new file mode 100644 index 00000000000000..14b64f56f31ff9 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.test.ts @@ -0,0 +1,104 @@ +/* + * 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 { httpServiceMock } from '../../../../../../../src/core/public/mocks'; +import { resolveRule } from './resolve_rule'; +import uuid from 'uuid'; + +const http = httpServiceMock.createStartContract(); + +describe('resolveRule', () => { + test('should call get API with base parameters', async () => { + const ruleId = `${uuid.v4()}/`; + const ruleIdEncoded = encodeURIComponent(ruleId); + const resolvedValue = { + id: '1/', + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'canvas-element.@created', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: ['sdfsdf'], + name: 'dfsdfdsf', + enabled: true, + throttle: '1h', + rule_type_id: '.index-threshold', + created_by: 'elastic', + updated_by: 'elastic', + created_at: '2021-04-01T20:29:18.652Z', + updated_at: '2021-04-01T20:33:38.260Z', + api_key_owner: 'elastic', + notify_when: 'onThrottleInterval', + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: '1', + execution_status: { status: 'ok', last_execution_date: '2021-04-01T21:16:46.709Z' }, + actions: [ + { + group: 'threshold met', + id: '1', + params: { documents: [{ dsfsdf: 1212 }] }, + connector_type_id: '.index', + }, + ], + outcome: 'aliasMatch', + alias_target_id: '2', + }; + http.get.mockResolvedValueOnce(resolvedValue); + + expect(await resolveRule({ http, ruleId })).toEqual({ + id: '1/', + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'canvas-element.@created', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: ['sdfsdf'], + name: 'dfsdfdsf', + enabled: true, + throttle: '1h', + alertTypeId: '.index-threshold', + createdBy: 'elastic', + updatedBy: 'elastic', + createdAt: '2021-04-01T20:29:18.652Z', + updatedAt: '2021-04-01T20:33:38.260Z', + apiKeyOwner: 'elastic', + notifyWhen: 'onThrottleInterval', + muteAll: false, + mutedInstanceIds: [], + scheduledTaskId: '1', + executionStatus: { status: 'ok', lastExecutionDate: '2021-04-01T21:16:46.709Z' }, + actions: [ + { + group: 'threshold met', + id: '1', + params: { documents: [{ dsfsdf: 1212 }] }, + actionTypeId: '.index', + }, + ], + outcome: 'aliasMatch', + alias_target_id: '2', + }); + expect(http.get).toHaveBeenCalledWith(`/internal/alerting/rule/${ruleIdEncoded}/_resolve`); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts new file mode 100644 index 00000000000000..bc2a19d298f8a5 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api/resolve_rule.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { HttpSetup } from 'kibana/public'; +import { ResolvedRule } from '../../../types'; +import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; +import { transformResolvedRule } from './common_transformations'; + +export async function resolveRule({ + http, + ruleId, +}: { + http: HttpSetup; + ruleId: string; +}): Promise { + const res = await http.get( + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(ruleId)}/_resolve` + ); + return transformResolvedRule(res); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 41c70a6737fa02..441daad1a50bd0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -8,45 +8,150 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; +import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { act } from 'react-dom/test-utils'; import { createMemoryHistory, createLocation } from 'history'; import { ToastsApi } from 'kibana/public'; -import { AlertDetailsRoute, getAlertData } from './alert_details_route'; +import { AlertDetailsRoute, getRuleData } from './alert_details_route'; import { Alert } from '../../../../types'; import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; +import { spacesOssPluginMock } from 'src/plugins/spaces_oss/public/mocks'; +import { useKibana } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); +class NotFoundError extends Error { + public readonly body: { + statusCode: number; + name: string; + } = { + statusCode: 404, + name: 'Not found', + }; + + constructor(message: string | undefined) { + super(message); + } +} + describe('alert_details_route', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const spacesOssMock = spacesOssPluginMock.createStartContract(); + async function setup() { + const useKibanaMock = useKibana as jest.Mocked; + // eslint-disable-next-line react-hooks/rules-of-hooks + useKibanaMock().services.spacesOss = spacesOssMock; + } + it('render a loader while fetching data', () => { - const alert = mockAlert(); + const rule = mockRule(); expect( shallow( - + ).containsMatchingElement() ).toBeTruthy(); }); + + it('redirects to another page if fetched rule is an aliasMatch', async () => { + await setup(); + const rule = mockRule(); + const { loadAlert, resolveRule } = mockApis(); + + loadAlert.mockImplementationOnce(async () => { + throw new NotFoundError('OMG'); + }); + resolveRule.mockImplementationOnce(async () => ({ + ...rule, + id: 'new_id', + outcome: 'aliasMatch', + alias_target_id: rule.id, + })); + const wrapper = mountWithIntl( + + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(loadAlert).toHaveBeenCalledWith(rule.id); + expect(resolveRule).toHaveBeenCalledWith(rule.id); + expect((spacesOssMock as any).ui.redirectLegacyUrl).toHaveBeenCalledWith( + `insightsAndAlerting/triggersActions/rule/new_id`, + `rule` + ); + }); + + it('shows warning callout if fetched rule is a conflict', async () => { + await setup(); + const rule = mockRule(); + const ruleType = { + id: rule.alertTypeId, + name: 'type name', + authorizedConsumers: ['consumer'], + }; + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); + + loadAlert.mockImplementationOnce(async () => { + throw new NotFoundError('OMG'); + }); + loadAlertTypes.mockImplementationOnce(async () => [ruleType]); + loadActionTypes.mockImplementation(async () => []); + resolveRule.mockImplementationOnce(async () => ({ + ...rule, + id: 'new_id', + outcome: 'conflict', + alias_target_id: rule.id, + })); + const wrapper = mountWithIntl( + + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(loadAlert).toHaveBeenCalledWith(rule.id); + expect(resolveRule).toHaveBeenCalledWith(rule.id); + expect((spacesOssMock as any).ui.components.getLegacyUrlConflict).toHaveBeenCalledWith({ + currentObjectId: 'new_id', + objectNoun: 'rule', + otherObjectId: rule.id, + otherObjectPath: `insightsAndAlerting/triggersActions/rule/${rule.id}`, + }); + }); }); -describe('getAlertData useEffect handler', () => { +describe('getRuleData useEffect handler', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('fetches alert', async () => { - const alert = mockAlert(); - const { loadAlert, loadAlertTypes, loadActionTypes } = mockApis(); + it('fetches rule', async () => { + const rule = mockRule(); + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); - loadAlert.mockImplementationOnce(async () => alert); + loadAlert.mockImplementationOnce(async () => rule); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertData( - alert.id, + await getRuleData( + rule.id, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, @@ -54,45 +159,47 @@ describe('getAlertData useEffect handler', () => { toastNotifications ); - expect(loadAlert).toHaveBeenCalledWith(alert.id); - expect(setAlert).toHaveBeenCalledWith(alert); + expect(loadAlert).toHaveBeenCalledWith(rule.id); + expect(resolveRule).not.toHaveBeenCalled(); + expect(setAlert).toHaveBeenCalledWith(rule); }); - it('fetches alert and action types', async () => { - const actionType = { + it('fetches rule and connector types', async () => { + const connectorType = { id: '.server-log', name: 'Server log', enabled: true, }; - const alert = mockAlert({ + const rule = mockRule({ actions: [ { group: '', id: uuid.v4(), - actionTypeId: actionType.id, + actionTypeId: connectorType.id, params: {}, }, ], }); - const alertType = { - id: alert.alertTypeId, + const ruleType = { + id: rule.alertTypeId, name: 'type name', }; - const { loadAlert, loadAlertTypes, loadActionTypes } = mockApis(); + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); - loadAlert.mockImplementation(async () => alert); - loadAlertTypes.mockImplementation(async () => [alertType]); - loadActionTypes.mockImplementation(async () => [actionType]); + loadAlert.mockImplementation(async () => rule); + loadAlertTypes.mockImplementation(async () => [ruleType]); + loadActionTypes.mockImplementation(async () => [connectorType]); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertData( - alert.id, + await getRuleData( + rule.id, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, @@ -102,29 +209,76 @@ describe('getAlertData useEffect handler', () => { expect(loadAlertTypes).toHaveBeenCalledTimes(1); expect(loadActionTypes).toHaveBeenCalledTimes(1); + expect(resolveRule).not.toHaveBeenCalled(); + + expect(setAlert).toHaveBeenCalledWith(rule); + expect(setAlertType).toHaveBeenCalledWith(ruleType); + expect(setActionTypes).toHaveBeenCalledWith([connectorType]); + }); + + it('fetches rule using resolve if initial GET results in a 404 error', async () => { + const connectorType = { + id: '.server-log', + name: 'Server log', + enabled: true, + }; + const rule = mockRule({ + actions: [ + { + group: '', + id: uuid.v4(), + actionTypeId: connectorType.id, + params: {}, + }, + ], + }); + + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); + const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); + + loadAlert.mockImplementationOnce(async () => { + throw new NotFoundError('OMG'); + }); + resolveRule.mockImplementationOnce(async () => rule); + + const toastNotifications = ({ + addDanger: jest.fn(), + } as unknown) as ToastsApi; + await getRuleData( + rule.id, + loadAlert, + loadAlertTypes, + resolveRule, + loadActionTypes, + setAlert, + setAlertType, + setActionTypes, + toastNotifications + ); - expect(setAlertType).toHaveBeenCalledWith(alertType); - expect(setActionTypes).toHaveBeenCalledWith([actionType]); + expect(loadAlert).toHaveBeenCalledWith(rule.id); + expect(resolveRule).toHaveBeenCalledWith(rule.id); + expect(setAlert).toHaveBeenCalledWith(rule); }); - it('displays an error if the alert isnt found', async () => { - const actionType = { + it('displays an error if fetching the rule results in a non-404 error', async () => { + const connectorType = { id: '.server-log', name: 'Server log', enabled: true, }; - const alert = mockAlert({ + const rule = mockRule({ actions: [ { group: '', id: uuid.v4(), - actionTypeId: actionType.id, + actionTypeId: connectorType.id, params: {}, }, ], }); - const { loadAlert, loadAlertTypes, loadActionTypes } = mockApis(); + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); loadAlert.mockImplementation(async () => { @@ -134,10 +288,11 @@ describe('getAlertData useEffect handler', () => { const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertData( - alert.id, + await getRuleData( + rule.id, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, @@ -150,40 +305,41 @@ describe('getAlertData useEffect handler', () => { }); }); - it('displays an error if the alert type isnt loaded', async () => { - const actionType = { + it('displays an error if the rule type isnt loaded', async () => { + const connectorType = { id: '.server-log', name: 'Server log', enabled: true, }; - const alert = mockAlert({ + const rule = mockRule({ actions: [ { group: '', id: uuid.v4(), - actionTypeId: actionType.id, + actionTypeId: connectorType.id, params: {}, }, ], }); - const { loadAlert, loadAlertTypes, loadActionTypes } = mockApis(); + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); - loadAlert.mockImplementation(async () => alert); + loadAlert.mockImplementation(async () => rule); loadAlertTypes.mockImplementation(async () => { - throw new Error('OMG no alert type'); + throw new Error('OMG no rule type'); }); - loadActionTypes.mockImplementation(async () => [actionType]); + loadActionTypes.mockImplementation(async () => [connectorType]); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertData( - alert.id, + await getRuleData( + rule.id, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, @@ -192,48 +348,49 @@ describe('getAlertData useEffect handler', () => { ); expect(toastNotifications.addDanger).toHaveBeenCalledTimes(1); expect(toastNotifications.addDanger).toHaveBeenCalledWith({ - title: 'Unable to load rule: OMG no alert type', + title: 'Unable to load rule: OMG no rule type', }); }); - it('displays an error if the action type isnt loaded', async () => { - const actionType = { + it('displays an error if the connector type isnt loaded', async () => { + const connectorType = { id: '.server-log', name: 'Server log', enabled: true, }; - const alert = mockAlert({ + const rule = mockRule({ actions: [ { group: '', id: uuid.v4(), - actionTypeId: actionType.id, + actionTypeId: connectorType.id, params: {}, }, ], }); - const alertType = { - id: alert.alertTypeId, + const ruleType = { + id: rule.alertTypeId, name: 'type name', }; - const { loadAlert, loadAlertTypes, loadActionTypes } = mockApis(); + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); - loadAlert.mockImplementation(async () => alert); + loadAlert.mockImplementation(async () => rule); - loadAlertTypes.mockImplementation(async () => [alertType]); + loadAlertTypes.mockImplementation(async () => [ruleType]); loadActionTypes.mockImplementation(async () => { - throw new Error('OMG no action type'); + throw new Error('OMG no connector type'); }); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertData( - alert.id, + await getRuleData( + rule.id, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, @@ -242,46 +399,47 @@ describe('getAlertData useEffect handler', () => { ); expect(toastNotifications.addDanger).toHaveBeenCalledTimes(1); expect(toastNotifications.addDanger).toHaveBeenCalledWith({ - title: 'Unable to load rule: OMG no action type', + title: 'Unable to load rule: OMG no connector type', }); }); - it('displays an error if the alert type isnt found', async () => { - const actionType = { + it('displays an error if the rule type isnt found', async () => { + const connectorType = { id: '.server-log', name: 'Server log', enabled: true, }; - const alert = mockAlert({ + const rule = mockRule({ actions: [ { group: '', id: uuid.v4(), - actionTypeId: actionType.id, + actionTypeId: connectorType.id, params: {}, }, ], }); - const alertType = { + const ruleType = { id: uuid.v4(), name: 'type name', }; - const { loadAlert, loadAlertTypes, loadActionTypes } = mockApis(); + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); - loadAlert.mockImplementation(async () => alert); - loadAlertTypes.mockImplementation(async () => [alertType]); - loadActionTypes.mockImplementation(async () => [actionType]); + loadAlert.mockImplementation(async () => rule); + loadAlertTypes.mockImplementation(async () => [ruleType]); + loadActionTypes.mockImplementation(async () => [connectorType]); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertData( - alert.id, + await getRuleData( + rule.id, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, @@ -290,57 +448,58 @@ describe('getAlertData useEffect handler', () => { ); expect(toastNotifications.addDanger).toHaveBeenCalledTimes(1); expect(toastNotifications.addDanger).toHaveBeenCalledWith({ - title: `Unable to load rule: Invalid Alert Type: ${alert.alertTypeId}`, + title: `Unable to load rule: Invalid Rule Type: ${rule.alertTypeId}`, }); }); it('displays an error if an action type isnt found', async () => { - const availableActionType = { + const availableConnectorType = { id: '.server-log', name: 'Server log', enabled: true, }; - const missingActionType = { + const missingConnectorType = { id: '.noop', name: 'No Op', enabled: true, }; - const alert = mockAlert({ + const rule = mockRule({ actions: [ { group: '', id: uuid.v4(), - actionTypeId: availableActionType.id, + actionTypeId: availableConnectorType.id, params: {}, }, { group: '', id: uuid.v4(), - actionTypeId: missingActionType.id, + actionTypeId: missingConnectorType.id, params: {}, }, ], }); - const alertType = { + const ruleType = { id: uuid.v4(), name: 'type name', }; - const { loadAlert, loadAlertTypes, loadActionTypes } = mockApis(); + const { loadAlert, loadAlertTypes, loadActionTypes, resolveRule } = mockApis(); const { setAlert, setAlertType, setActionTypes } = mockStateSetter(); - loadAlert.mockImplementation(async () => alert); - loadAlertTypes.mockImplementation(async () => [alertType]); - loadActionTypes.mockImplementation(async () => [availableActionType]); + loadAlert.mockImplementation(async () => rule); + loadAlertTypes.mockImplementation(async () => [ruleType]); + loadActionTypes.mockImplementation(async () => [availableConnectorType]); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertData( - alert.id, + await getRuleData( + rule.id, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, @@ -349,7 +508,7 @@ describe('getAlertData useEffect handler', () => { ); expect(toastNotifications.addDanger).toHaveBeenCalledTimes(1); expect(toastNotifications.addDanger).toHaveBeenCalledWith({ - title: `Unable to load rule: Invalid Action Type: ${missingActionType.id}`, + title: `Unable to load rule: Invalid Connector Type: ${missingConnectorType.id}`, }); }); }); @@ -359,6 +518,7 @@ function mockApis() { loadAlert: jest.fn(), loadAlertTypes: jest.fn(), loadActionTypes: jest.fn(), + resolveRule: jest.fn(), }; } @@ -370,23 +530,23 @@ function mockStateSetter() { }; } -function mockRouterProps(alert: Alert) { +function mockRouterProps(rule: Alert) { return { match: { isExact: false, - path: `/rule/${alert.id}`, + path: `/rule/${rule.id}`, url: '', - params: { ruleId: alert.id }, + params: { ruleId: rule.id }, }, history: createMemoryHistory(), - location: createLocation(`/rule/${alert.id}`), + location: createLocation(`/rule/${rule.id}`), }; } -function mockAlert(overloads: Partial = {}): Alert { +function mockRule(overloads: Partial = {}): Alert { return { id: uuid.v4(), enabled: true, - name: `alert-${uuid.v4()}`, + name: `rule-${uuid.v4()}`, tags: [], alertTypeId: '.noop', consumer: 'consumer', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx index 2d6db5f6330ccc..b6279d7fca1007 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx @@ -9,7 +9,8 @@ import { i18n } from '@kbn/i18n'; import React, { useState, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { ToastsApi } from 'kibana/public'; -import { Alert, AlertType, ActionType } from '../../../../types'; +import { EuiSpacer } from '@elastic/eui'; +import { Alert, AlertType, ActionType, ResolvedRule } from '../../../../types'; import { AlertDetailsWithApi as AlertDetails } from './alert_details'; import { throwIfAbsent, throwIfIsntContained } from '../../../lib/value_validators'; import { @@ -27,7 +28,7 @@ type AlertDetailsRouteProps = RouteComponentProps<{ ruleId: string; }> & Pick & - Pick; + Pick; export const AlertDetailsRoute: React.FunctionComponent = ({ match: { @@ -36,63 +37,127 @@ export const AlertDetailsRoute: React.FunctionComponent loadAlert, loadAlertTypes, loadActionTypes, + resolveRule, }) => { const { http, notifications: { toasts }, + spacesOss, } = useKibana().services; - const [alert, setAlert] = useState(null); + const { basePath } = http; + + const [alert, setAlert] = useState(null); const [alertType, setAlertType] = useState(null); const [actionTypes, setActionTypes] = useState(null); const [refreshToken, requestRefresh] = React.useState(); useEffect(() => { - getAlertData( + getRuleData( ruleId, loadAlert, loadAlertTypes, + resolveRule, loadActionTypes, setAlert, setAlertType, setActionTypes, toasts ); - }, [ruleId, http, loadActionTypes, loadAlert, loadAlertTypes, toasts, refreshToken]); + }, [ruleId, http, loadActionTypes, loadAlert, loadAlertTypes, resolveRule, toasts, refreshToken]); + + useEffect(() => { + if (alert) { + const outcome = (alert as ResolvedRule).outcome; + if (outcome === 'aliasMatch' && spacesOss.isSpacesAvailable) { + // This rule has been resolved from a legacy URL - redirect the user to the new URL and display a toast. + const path = basePath.prepend(`insightsAndAlerting/triggersActions/rule/${alert.id}`); + spacesOss.ui.redirectLegacyUrl( + path, + i18n.translate('xpack.triggersActionsUI.sections.alertDetails.redirectObjectNoun', { + defaultMessage: 'rule', + }) + ); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [alert]); + + const getLegacyUrlConflictCallout = () => { + const outcome = (alert as ResolvedRule).outcome; + const aliasTargetId = (alert as ResolvedRule).alias_target_id; + if (outcome === 'conflict' && aliasTargetId && spacesOss.isSpacesAvailable) { + // We have resolved to one rule, but there is another one with a legacy URL associated with this page. Display a + // callout with a warning for the user, and provide a way for them to navigate to the other rule. + const otherRulePath = basePath.prepend( + `insightsAndAlerting/triggersActions/rule/${aliasTargetId}` + ); + return ( + <> + + {spacesOss.ui.components.getLegacyUrlConflict({ + objectNoun: i18n.translate( + 'xpack.triggersActionsUI.sections.alertDetails.redirectObjectNoun', + { + defaultMessage: 'rule', + } + ), + currentObjectId: alert?.id!, + otherObjectId: aliasTargetId, + otherObjectPath: otherRulePath, + })} + + ); + } + return null; + }; return alert && alertType && actionTypes ? ( - requestRefresh(Date.now())} - /> + <> + {getLegacyUrlConflictCallout()} + requestRefresh(Date.now())} + /> + ) : ( ); }; -export async function getAlertData( - alertId: string, +export async function getRuleData( + ruleId: string, loadAlert: AlertApis['loadAlert'], loadAlertTypes: AlertApis['loadAlertTypes'], + resolveRule: AlertApis['resolveRule'], loadActionTypes: ActionApis['loadActionTypes'], - setAlert: React.Dispatch>, + setAlert: React.Dispatch>, setAlertType: React.Dispatch>, setActionTypes: React.Dispatch>, toasts: Pick ) { try { - const loadedAlert = await loadAlert(alertId); - setAlert(loadedAlert); + let loadedRule: Alert | ResolvedRule; + try { + loadedRule = await loadAlert(ruleId); + } catch (err) { + // Try resolving this rule id if the error is a 404, otherwise re-throw + if (err?.body?.statusCode !== 404) { + throw err; + } + loadedRule = await resolveRule(ruleId); + } + setAlert(loadedRule); const [loadedAlertType, loadedActionTypes] = await Promise.all([ loadAlertTypes() - .then((types) => types.find((type) => type.id === loadedAlert.alertTypeId)) - .then(throwIfAbsent(`Invalid Alert Type: ${loadedAlert.alertTypeId}`)), + .then((types) => types.find((type) => type.id === loadedRule.alertTypeId)) + .then(throwIfAbsent(`Invalid Rule Type: ${loadedRule.alertTypeId}`)), loadActionTypes().then( throwIfIsntContained( - new Set(loadedAlert.actions.map((action) => action.actionTypeId)), - (requiredActionType: string) => `Invalid Action Type: ${requiredActionType}`, + new Set(loadedRule.actions.map((action) => action.actionTypeId)), + (requiredActionType: string) => `Invalid Connector Type: ${requiredActionType}`, (action: ActionType) => action.id ) ), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx index 7d314cef55680d..806f649e7d033b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.test.tsx @@ -36,6 +36,7 @@ describe('with_bulk_alert_api_operations', () => { expect(typeof props.deleteAlert).toEqual('function'); expect(typeof props.loadAlert).toEqual('function'); expect(typeof props.loadAlertTypes).toEqual('function'); + expect(typeof props.resolveRule).toEqual('function'); return
; }; @@ -220,6 +221,24 @@ describe('with_bulk_alert_api_operations', () => { expect(alertApi.loadAlert).toHaveBeenCalledWith({ alertId, http }); }); + it('resolveRule calls the resolveRule api', () => { + const { http } = useKibanaMock().services; + const ComponentToExtend = ({ + resolveRule, + ruleId, + }: ComponentOpts & { ruleId: Alert['id'] }) => { + return ; + }; + + const ExtendedComponent = withBulkAlertOperations(ComponentToExtend); + const ruleId = uuid.v4(); + const component = mount(); + component.find('button').simulate('click'); + + expect(alertApi.resolveRule).toHaveBeenCalledTimes(1); + expect(alertApi.resolveRule).toHaveBeenCalledWith({ ruleId, http }); + }); + it('loadAlertTypes calls the loadAlertTypes api', () => { const { http } = useKibanaMock().services; const ComponentToExtend = ({ loadAlertTypes }: ComponentOpts) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx index 983fe5641e62b1..59919c202277c9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx @@ -13,6 +13,7 @@ import { AlertTaskState, AlertInstanceSummary, AlertingFrameworkHealth, + ResolvedRule, } from '../../../../types'; import { deleteAlerts, @@ -31,6 +32,7 @@ import { loadAlertInstanceSummary, loadAlertTypes, alertingFrameworkHealth, + resolveRule, } from '../../../lib/alert_api'; import { useKibana } from '../../../../common/lib/kibana'; @@ -62,6 +64,7 @@ export interface ComponentOpts { loadAlertInstanceSummary: (id: Alert['id']) => Promise; loadAlertTypes: () => Promise; getHealth: () => Promise; + resolveRule: (id: Alert['id']) => Promise; } export type PropsWithOptionalApiHandlers = Omit & Partial; @@ -132,6 +135,7 @@ export function withBulkAlertOperations( loadAlertInstanceSummary({ http, alertId }) } loadAlertTypes={async () => loadAlertTypes({ http })} + resolveRule={async (ruleId: Alert['id']) => resolveRule({ http, ruleId })} getHealth={async () => alertingFrameworkHealth({ http })} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts index a1a0184198dfd4..f8aa483711c309 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts @@ -8,7 +8,7 @@ import React from 'react'; import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; - +import { spacesOssPluginMock } from '../../../../../../../src/plugins/spaces_oss/public/mocks'; import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; import { TriggersAndActionsUiServices } from '../../../application/app'; @@ -45,6 +45,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => { element: ({ style: { cursor: 'pointer' }, } as unknown) as HTMLElement, + spacesOss: spacesOssPluginMock.createStartContract(), } as TriggersAndActionsUiServices; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 7661eefba7f650..36d6964ce77532 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -26,6 +26,7 @@ import { PluginStartContract as AlertingStart } from '../../alerting/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; import type { SpacesPluginStart } from '../../spaces/public'; +import type { SpacesOssPluginStart } from '../../../../src/plugins/spaces_oss/public'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; @@ -73,6 +74,7 @@ interface PluginsStart { charts: ChartsPluginStart; alerting?: AlertingStart; spaces?: SpacesPluginStart; + spacesOss: SpacesOssPluginStart; navigateToApp: CoreStart['application']['navigateToApp']; features: FeaturesPluginStart; } @@ -148,6 +150,7 @@ export class Plugin charts: pluginsStart.charts, alerting: pluginsStart.alerting, spaces: pluginsStart.spaces, + spacesOss: pluginsStart.spacesOss, element: params.element, storage: new Storage(window.localStorage), setBreadcrumbs: params.setBreadcrumbs, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index f01967592ea8c1..ae4fd5152794f6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -24,6 +24,7 @@ import { ActionGroup, AlertActionParam, SanitizedAlert, + ResolvedSanitizedRule, AlertAction, AlertAggregations, AlertTaskState, @@ -40,6 +41,7 @@ import { // In Triggers and Actions we treat all `Alert`s as `SanitizedAlert` // so the `Params` is a black-box of Record type Alert = SanitizedAlert; +type ResolvedRule = ResolvedSanitizedRule; export { Alert, @@ -52,6 +54,7 @@ export { AlertingFrameworkHealth, AlertNotifyWhenType, AlertTypeParams, + ResolvedRule, }; export { ActionType, diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index 6536206acf3695..e3c8b77d2c1d45 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -24,5 +24,6 @@ { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/management/tsconfig.json" }, + { "path": "../../../src/plugins/spaces_oss/tsconfig.json" }, ] } From 4d0623ee25b288609742ad6fd5e5776c0f71ccce Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Thu, 19 Aug 2021 00:28:52 -0500 Subject: [PATCH 29/36] [ML] Add tooltip help text for Transform filter query control (#108720) * [ML] Add tooltip help text * [ML] Remove unused import * [ML] Update tooltip content to be clearer Co-authored-by: Lisa Cawley Co-authored-by: Lisa Cawley Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../filter_agg/components/filter_agg_form.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx index e3e767a81b01d0..543ecc7dee1950 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx @@ -6,7 +6,7 @@ */ import React, { useContext, useMemo } from 'react'; -import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { EuiFormRow, EuiIcon, EuiSelect, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import { CreateTransformWizardContext } from '../../../../wizard/wizard'; @@ -72,10 +72,22 @@ export const FilterAggForm: PivotAggsConfigFilter['AggFormComponent'] = ({ <> + <> + + + } + > + + + } > Date: Thu, 19 Aug 2021 09:04:03 +0300 Subject: [PATCH 30/36] Fixed flakiness with visualize custom role (#109077) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/visualize/feature_controls/visualize_security.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts index 2994e18fa9ab77..7bfae9ba36be42 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts @@ -32,6 +32,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/visualize/default'); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); + // ensure we're logged out so we can login as the appropriate users + await PageObjects.security.forceLogout(); }); after(async () => { @@ -81,7 +83,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows visualize navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Overview', 'Visualize Library', 'Stack Management']); + expect(navLinks).to.contain('Visualize Library'); }); it(`landing page shows "Create new Visualization" button`, async () => { From 2172b88daef63777b20b282e74f394a372129453 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 19 Aug 2021 09:04:53 +0300 Subject: [PATCH 31/36] Fix flakiness in load and save agg-based visualizations (#109066) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/functional/page_objects/visualize_page.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 1271fe5108f56d..cf3a692d1622e0 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -310,6 +310,7 @@ export class VisualizePageObject extends FtrService { if (navigateToVisualize) { await this.clickLoadSavedVisButton(); } + await this.listingTable.searchForItemWithName(vizName); await this.openSavedVisualization(vizName); } From 544c41e21418dd5e3ff73c9e879f035594b1f359 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 19 Aug 2021 08:18:28 +0200 Subject: [PATCH 32/36] [Discover] Unskip discover tab field data functional tests (#107305) --- test/functional/apps/discover/_data_grid.ts | 4 +-- .../apps/discover/_data_grid_doc_table.ts | 2 +- test/functional/apps/discover/_discover.ts | 2 +- test/functional/apps/discover/_doc_table.ts | 2 +- test/functional/apps/discover/_field_data.ts | 3 +- .../discover/_field_data_with_fields_api.ts | 3 +- test/functional/page_objects/discover_page.ts | 30 ++++++++++++++++--- 7 files changed, 33 insertions(+), 13 deletions(-) diff --git a/test/functional/apps/discover/_data_grid.ts b/test/functional/apps/discover/_data_grid.ts index efd97fce3f7f5d..4a343fb30384e3 100644 --- a/test/functional/apps/discover/_data_grid.ts +++ b/test/functional/apps/discover/_data_grid.ts @@ -47,10 +47,10 @@ export default function ({ await PageObjects.discover.clickFieldListItemAdd('agent'); expect(await getTitles()).to.be('Time (@timestamp) bytes agent'); - await PageObjects.discover.clickFieldListItemAdd('bytes'); + await PageObjects.discover.clickFieldListItemRemove('bytes'); expect(await getTitles()).to.be('Time (@timestamp) agent'); - await PageObjects.discover.clickFieldListItemAdd('agent'); + await PageObjects.discover.clickFieldListItemRemove('agent'); expect(await getTitles()).to.be('Time (@timestamp) Document'); }); }); diff --git a/test/functional/apps/discover/_data_grid_doc_table.ts b/test/functional/apps/discover/_data_grid_doc_table.ts index 00e6a5025e2af6..2efb1ba51811fd 100644 --- a/test/functional/apps/discover/_data_grid_doc_table.ts +++ b/test/functional/apps/discover/_data_grid_doc_table.ts @@ -161,7 +161,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); } // remove the second column - await PageObjects.discover.clickFieldListItemAdd(extraColumns[1]); + await PageObjects.discover.clickFieldListItemRemove(extraColumns[1]); await PageObjects.header.waitUntilLoadingHasFinished(); // test that the second column is no longer there const header = await dataGrid.getHeaderFields(); diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index 8dc695abc8d45f..4704f75f34289e 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -310,7 +310,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickFieldSort('_score', 'Sort Low-High'); const currentUrlWithScore = await browser.getCurrentUrl(); expect(currentUrlWithScore).to.contain('_score'); - await PageObjects.discover.clickFieldListItemAdd('_score'); + await PageObjects.discover.clickFieldListItemRemove('_score'); const currentUrlWithoutScore = await browser.getCurrentUrl(); expect(currentUrlWithoutScore).not.to.contain('_score'); }); diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 09a162e051bf6e..f01d6b18fbf01b 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -218,7 +218,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); } // remove the second column - await PageObjects.discover.clickFieldListItemAdd(extraColumns[1]); + await PageObjects.discover.clickFieldListItemRemove(extraColumns[1]); await PageObjects.header.waitUntilLoadingHasFinished(); // test that the second column is no longer there const docHeader = await find.byCssSelector('thead > tr:nth-child(1)'); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index fc0c0c6a48649c..27407e9a0bc4d8 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -34,8 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - // FLAKY: https://github.com/elastic/kibana/issues/100437 - describe.skip('field data', function () { + describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; await retry.try(async function () { diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 97c1e678c4a9f2..666377ae7f7942 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -34,8 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - // FLAKY: https://github.com/elastic/kibana/issues/103389 - describe.skip('field data', function () { + describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; await retry.try(async function () { diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index a4d8f884e18244..ae1b4fbf3179ad 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -353,17 +353,39 @@ export class DiscoverPageObject extends FtrService { public async clickFieldListItemAdd(field: string) { // a filter check may make sense here, but it should be properly handled to make // it work with the _score and _source fields as well + if (await this.isFieldSelected(field)) { + return; + } await this.clickFieldListItemToggle(field); + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + await this.retry.waitFor(`field ${field} to be added to classic table`, async () => { + return await this.testSubjects.exists(`docTableHeader-${field}`); + }); + } else { + await this.retry.waitFor(`field ${field} to be added to new table`, async () => { + return await this.testSubjects.exists(`dataGridHeaderCell-${field}`); + }); + } } - public async clickFieldListItemRemove(field: string) { + public async isFieldSelected(field: string) { if (!(await this.testSubjects.exists('fieldList-selected'))) { - return; + return false; } const selectedList = await this.testSubjects.find('fieldList-selected'); - if (await this.testSubjects.descendantExists(`field-${field}`, selectedList)) { - await this.clickFieldListItemToggle(field); + return await this.testSubjects.descendantExists(`field-${field}`, selectedList); + } + + public async clickFieldListItemRemove(field: string) { + if ( + !(await this.testSubjects.exists('fieldList-selected')) || + !(await this.isFieldSelected(field)) + ) { + return; } + + await this.clickFieldListItemToggle(field); } public async clickFieldListItemVisualize(fieldName: string) { From fe08d0aa21fef7f9b7bb7d4de0c57c225f271a67 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 19 Aug 2021 11:27:18 +0300 Subject: [PATCH 33/36] [TSVB] Long legend values support (#108023) * [TSVB] Supports legends with long values * Add a unit test * Design optimization * Revert changes * Add the missing prop type * Ensure that the limits are respected Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/panel_model.ts | 2 + .../panel_config/timeseries.test.tsx | 82 ++++++++++++ .../components/panel_config/timeseries.tsx | 122 ++++++++++++------ .../components/vis_types/timeseries/vis.js | 2 + .../visualizations/views/timeseries/index.js | 10 ++ .../public/metrics_type.ts | 2 + x-pack/yarn.lock | 27 ---- 7 files changed, 184 insertions(+), 63 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.test.tsx diff --git a/src/plugins/vis_type_timeseries/common/types/panel_model.ts b/src/plugins/vis_type_timeseries/common/types/panel_model.ts index 2ac9125534ac79..ff942a30abbdc0 100644 --- a/src/plugins/vis_type_timeseries/common/types/panel_model.ts +++ b/src/plugins/vis_type_timeseries/common/types/panel_model.ts @@ -161,6 +161,8 @@ export interface Panel { series: Series[]; show_grid: number; show_legend: number; + truncate_legend?: number; + max_lines_legend?: number; time_field?: string; time_range_mode?: string; tooltip_mode?: TOOLTIP_MODES; diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.test.tsx new file mode 100644 index 00000000000000..02f28f31358807 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.test.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallowWithIntl as shallow } from '@kbn/test/jest'; + +jest.mock('../lib/get_default_query_language', () => ({ + getDefaultQueryLanguage: () => 'kuery', +})); + +import { TimeseriesPanelConfig } from './timeseries'; +import { PanelConfigProps } from './types'; + +describe('TimeseriesPanelConfig', () => { + it('sets the number input to the given value', () => { + const props = ({ + fields: {}, + model: { + max_lines_legend: 2, + }, + onChange: jest.fn(), + } as unknown) as PanelConfigProps; + const wrapper = shallow(); + wrapper.instance().setState({ selectedTab: 'options' }); + expect( + wrapper.find('[data-test-subj="timeSeriesEditorDataMaxLegendLines"]').prop('value') + ).toEqual(2); + }); + + it('switches on the truncate legend switch if the prop is set to 1 ', () => { + const props = ({ + fields: {}, + model: { + max_lines_legend: 2, + truncate_legend: 1, + }, + onChange: jest.fn(), + } as unknown) as PanelConfigProps; + const wrapper = shallow(); + wrapper.instance().setState({ selectedTab: 'options' }); + expect( + wrapper.find('[data-test-subj="timeSeriesEditorDataTruncateLegendSwitch"]').prop('value') + ).toEqual(1); + }); + + it('switches off the truncate legend switch if the prop is set to 0', () => { + const props = ({ + fields: {}, + model: { + max_lines_legend: 2, + truncate_legend: 0, + }, + onChange: jest.fn(), + } as unknown) as PanelConfigProps; + const wrapper = shallow(); + wrapper.instance().setState({ selectedTab: 'options' }); + expect( + wrapper.find('[data-test-subj="timeSeriesEditorDataTruncateLegendSwitch"]').prop('value') + ).toEqual(0); + }); + + it('disables the max lines number input if the truncate legend switch is off', () => { + const props = ({ + fields: {}, + model: { + max_lines_legend: 2, + truncate_legend: 0, + }, + onChange: jest.fn(), + } as unknown) as PanelConfigProps; + const wrapper = shallow(); + wrapper.instance().setState({ selectedTab: 'options' }); + expect( + wrapper.find('[data-test-subj="timeSeriesEditorDataMaxLegendLines"]').prop('disabled') + ).toEqual(true); + }); +}); diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx index cdad8c1aeff4b1..25e6c7906d8317 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx @@ -23,6 +23,7 @@ import { EuiFieldText, EuiTitle, EuiHorizontalRule, + EuiFieldNumber, } from '@elastic/eui'; // @ts-expect-error not typed yet @@ -102,6 +103,9 @@ const legendPositionOptions = [ }, ]; +const MAX_TRUNCATE_LINES = 5; +const MIN_TRUNCATE_LINES = 1; + export class TimeseriesPanelConfig extends Component< PanelConfigProps, { selectedTab: PANEL_CONFIG_TABS } @@ -344,7 +348,7 @@ export class TimeseriesPanelConfig extends Component< /> - + - - + - + - + + + + - + + + - - - + + + + + + - + + + + + + + + + + { + const val = Number(e.target.value); + this.props.onChange({ + max_lines_legend: Math.min( + MAX_TRUNCATE_LINES, + Math.max(val, MIN_TRUNCATE_LINES) + ), + }); + }} + /> + + + + + + + diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index 097b0a7b5e3327..d9440804701b2a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -238,6 +238,8 @@ class TimeseriesVisualization extends Component { showGrid={Boolean(model.show_grid)} legend={Boolean(model.show_legend)} legendPosition={model.legend_position} + truncateLegend={Boolean(model.truncate_legend)} + maxLegendLines={model.max_lines_legend} tooltipMode={model.tooltip_mode} xAxisFormatter={this.xAxisFormatter(interval)} annotations={this.prepareAnnotations()} diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index a818d1d5843dea..b470352eec56a4 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -56,6 +56,8 @@ export const TimeSeries = ({ showGrid, legend, legendPosition, + truncateLegend, + maxLegendLines, tooltipMode, series, yAxis, @@ -172,6 +174,9 @@ export const TimeSeries = ({ background: { color: backgroundColor, }, + legend: { + labelOptions: { maxLines: truncateLegend ? maxLegendLines ?? 1 : 0 }, + }, }, chartTheme, ]} @@ -216,6 +221,7 @@ export const TimeSeries = ({ lines, data, hideInLegend, + truncateLegend, xScaleType, yScaleType, groupId, @@ -249,6 +255,7 @@ export const TimeSeries = ({ name={getValueOrEmpty(seriesName)} data={data} hideInLegend={hideInLegend} + truncateLegend={truncateLegend} bars={bars} color={finalColor} stackAccessors={stackAccessors} @@ -274,6 +281,7 @@ export const TimeSeries = ({ name={getValueOrEmpty(seriesName)} data={data} hideInLegend={hideInLegend} + truncateLegend={truncateLegend} lines={lines} color={finalColor} stackAccessors={stackAccessors} @@ -336,6 +344,8 @@ TimeSeries.propTypes = { showGrid: PropTypes.bool, legend: PropTypes.bool, legendPosition: PropTypes.string, + truncateLegend: PropTypes.bool, + maxLegendLines: PropTypes.number, series: PropTypes.array, yAxis: PropTypes.array, onBrush: PropTypes.func, diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index d639604c7cd290..b68812b9828e33 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -93,6 +93,8 @@ export const metricsVisDefinition: VisTypeDefinition< axis_formatter: 'number', axis_scale: 'normal', show_legend: 1, + truncate_legend: 1, + max_lines_legend: 1, show_grid: 1, tooltip_mode: TOOLTIP_MODES.SHOW_ALL, drop_last_bucket: 0, diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index def2ba279bfff5..fb57ccd13afbd0 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -2,30 +2,3 @@ # yarn lockfile v1 -"@kbn/interpreter@link:../packages/kbn-interpreter": - version "0.0.0" - uid "" - -"@kbn/optimizer@link:../packages/kbn-optimizer": - version "0.0.0" - uid "" - -"@kbn/plugin-helpers@link:../packages/kbn-plugin-helpers": - version "0.0.0" - uid "" - -"@kbn/storybook@link:../packages/kbn-storybook": - version "0.0.0" - uid "" - -"@kbn/test@link:../packages/kbn-test": - version "0.0.0" - uid "" - -"@kbn/ui-framework@link:../packages/kbn-ui-framework": - version "0.0.0" - uid "" - -"@kbn/ui-shared-deps@link:../packages/kbn-ui-shared-deps": - version "0.0.0" - uid "" From cb0ce593765eb71419f343d987aa4d09c0ffe77b Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Thu, 19 Aug 2021 11:24:32 +0200 Subject: [PATCH 34/36] Introduce `Enroll` API endpoint. (#108835) --- .../common/elasticsearch_connection_status.ts | 5 - .../interactive_setup/server/config.test.ts | 43 ++ .../interactive_setup/server/config.ts | 10 + .../server/elasticsearch_service.mock.ts | 20 + .../server/elasticsearch_service.test.ts | 497 ++++++++++++++++++ .../server/elasticsearch_service.ts | 239 +++++++++ .../interactive_setup/server/errors.test.ts | 57 ++ .../interactive_setup/server/errors.ts | 35 ++ .../server/kibana_config_writer.mock.ts | 18 + .../server/kibana_config_writer.test.ts | 140 +++++ .../server/kibana_config_writer.ts | 93 ++++ src/plugins/interactive_setup/server/mocks.ts | 26 + .../interactive_setup/server/plugin.ts | 84 +-- .../server/routes/enroll.test.ts | 305 +++++++++++ .../interactive_setup/server/routes/enroll.ts | 89 +++- .../server/routes/index.mock.ts | 26 + .../interactive_setup/server/routes/index.ts | 12 +- 17 files changed, 1650 insertions(+), 49 deletions(-) create mode 100644 src/plugins/interactive_setup/server/config.test.ts create mode 100644 src/plugins/interactive_setup/server/elasticsearch_service.mock.ts create mode 100644 src/plugins/interactive_setup/server/elasticsearch_service.test.ts create mode 100644 src/plugins/interactive_setup/server/elasticsearch_service.ts create mode 100644 src/plugins/interactive_setup/server/errors.test.ts create mode 100644 src/plugins/interactive_setup/server/errors.ts create mode 100644 src/plugins/interactive_setup/server/kibana_config_writer.mock.ts create mode 100644 src/plugins/interactive_setup/server/kibana_config_writer.test.ts create mode 100644 src/plugins/interactive_setup/server/kibana_config_writer.ts create mode 100644 src/plugins/interactive_setup/server/mocks.ts create mode 100644 src/plugins/interactive_setup/server/routes/enroll.test.ts create mode 100644 src/plugins/interactive_setup/server/routes/index.mock.ts diff --git a/src/plugins/interactive_setup/common/elasticsearch_connection_status.ts b/src/plugins/interactive_setup/common/elasticsearch_connection_status.ts index 4e1506f69990ca..bc0b172dfe234d 100644 --- a/src/plugins/interactive_setup/common/elasticsearch_connection_status.ts +++ b/src/plugins/interactive_setup/common/elasticsearch_connection_status.ts @@ -10,11 +10,6 @@ * Describes current status of the Elasticsearch connection. */ export enum ElasticsearchConnectionStatus { - /** - * Indicates that Kibana hasn't figured out yet if existing Elasticsearch connection configuration is valid. - */ - Unknown = 'unknown', - /** * Indicates that current Elasticsearch connection configuration valid and sufficient. */ diff --git a/src/plugins/interactive_setup/server/config.test.ts b/src/plugins/interactive_setup/server/config.test.ts new file mode 100644 index 00000000000000..b8ae673ad28f91 --- /dev/null +++ b/src/plugins/interactive_setup/server/config.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ConfigSchema } from './config'; + +describe('config schema', () => { + it('generates proper defaults', () => { + expect(ConfigSchema.validate({})).toMatchInlineSnapshot(` + Object { + "connectionCheck": Object { + "interval": "PT5S", + }, + "enabled": false, + } + `); + }); + + describe('#connectionCheck', () => { + it('should properly set required connection check interval', () => { + expect(ConfigSchema.validate({ connectionCheck: { interval: '1s' } })).toMatchInlineSnapshot(` + Object { + "connectionCheck": Object { + "interval": "PT1S", + }, + "enabled": false, + } + `); + }); + + it('should throw error if interactiveSetup.connectionCheck.interval is less than 1 second', () => { + expect(() => + ConfigSchema.validate({ connectionCheck: { interval: 100 } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[connectionCheck.interval]: the value must be greater or equal to 1 second."` + ); + }); + }); +}); diff --git a/src/plugins/interactive_setup/server/config.ts b/src/plugins/interactive_setup/server/config.ts index b16c51bcbda09d..9986f16e9ce933 100644 --- a/src/plugins/interactive_setup/server/config.ts +++ b/src/plugins/interactive_setup/server/config.ts @@ -13,4 +13,14 @@ export type ConfigType = TypeOf; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), + connectionCheck: schema.object({ + interval: schema.duration({ + defaultValue: '5s', + validate(value) { + if (value.asSeconds() < 1) { + return 'the value must be greater or equal to 1 second.'; + } + }, + }), + }), }); diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.mock.ts b/src/plugins/interactive_setup/server/elasticsearch_service.mock.ts new file mode 100644 index 00000000000000..8bc7e4307e76f8 --- /dev/null +++ b/src/plugins/interactive_setup/server/elasticsearch_service.mock.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BehaviorSubject } from 'rxjs'; + +import { ElasticsearchConnectionStatus } from '../common'; + +export const elasticsearchServiceMock = { + createSetup: () => ({ + connectionStatus$: new BehaviorSubject( + ElasticsearchConnectionStatus.Configured + ), + enroll: jest.fn(), + }), +}; diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts new file mode 100644 index 00000000000000..b8eb7293fd678a --- /dev/null +++ b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts @@ -0,0 +1,497 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors } from '@elastic/elasticsearch'; + +import { nextTick } from '@kbn/test/jest'; +import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; + +import { ElasticsearchConnectionStatus } from '../common'; +import { ConfigSchema } from './config'; +import type { ElasticsearchServiceSetup } from './elasticsearch_service'; +import { ElasticsearchService } from './elasticsearch_service'; +import { interactiveSetupMock } from './mocks'; + +describe('ElasticsearchService', () => { + let service: ElasticsearchService; + let mockElasticsearchPreboot: ReturnType; + beforeEach(() => { + service = new ElasticsearchService(loggingSystemMock.createLogger()); + mockElasticsearchPreboot = elasticsearchServiceMock.createPreboot(); + }); + + describe('#setup()', () => { + let mockConnectionStatusClient: ReturnType< + typeof elasticsearchServiceMock.createCustomClusterClient + >; + let mockEnrollClient: ReturnType; + let mockAuthenticateClient: ReturnType< + typeof elasticsearchServiceMock.createCustomClusterClient + >; + let setupContract: ElasticsearchServiceSetup; + beforeEach(() => { + mockConnectionStatusClient = elasticsearchServiceMock.createCustomClusterClient(); + mockEnrollClient = elasticsearchServiceMock.createCustomClusterClient(); + mockAuthenticateClient = elasticsearchServiceMock.createCustomClusterClient(); + mockElasticsearchPreboot.createClient.mockImplementation((type) => { + switch (type) { + case 'enroll': + return mockEnrollClient; + case 'authenticate': + return mockAuthenticateClient; + default: + return mockConnectionStatusClient; + } + }); + + setupContract = service.setup({ + elasticsearch: mockElasticsearchPreboot, + connectionCheckInterval: ConfigSchema.validate({}).connectionCheck.interval, + }); + }); + + describe('#connectionStatus$', () => { + beforeEach(() => jest.useFakeTimers()); + afterEach(() => jest.useRealTimers()); + + it('does not repeat ping request if have multiple subscriptions', async () => { + mockConnectionStatusClient.asInternalUser.ping.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + + const mockHandler1 = jest.fn(); + const mockHandler2 = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler1); + setupContract.connectionStatus$.subscribe(mockHandler2); + + jest.advanceTimersByTime(0); + await nextTick(); + + // Late subscription. + const mockHandler3 = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler3); + + jest.advanceTimersByTime(100); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(1); + expect(mockHandler1).toHaveBeenCalledTimes(1); + expect(mockHandler1).toHaveBeenCalledWith(ElasticsearchConnectionStatus.NotConfigured); + expect(mockHandler2).toHaveBeenCalledTimes(1); + expect(mockHandler2).toHaveBeenCalledWith(ElasticsearchConnectionStatus.NotConfigured); + expect(mockHandler3).toHaveBeenCalledTimes(1); + expect(mockHandler3).toHaveBeenCalledWith(ElasticsearchConnectionStatus.NotConfigured); + }); + + it('does not report the same status twice', async () => { + mockConnectionStatusClient.asInternalUser.ping.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + + const mockHandler = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler); + + jest.advanceTimersByTime(0); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith(ElasticsearchConnectionStatus.NotConfigured); + + mockHandler.mockClear(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(2); + expect(mockHandler).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(3); + expect(mockHandler).not.toHaveBeenCalled(); + }); + + it('stops status checks as soon as connection is known to be configured', async () => { + mockConnectionStatusClient.asInternalUser.ping.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + + const mockHandler = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler); + + jest.advanceTimersByTime(0); + await nextTick(); + + // Initial ping (connection error). + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith(ElasticsearchConnectionStatus.NotConfigured); + + // Repeated ping (Unauthorized error). + mockConnectionStatusClient.asInternalUser.ping.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: {} }) + ) + ); + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(2); + expect(mockHandler).toHaveBeenCalledTimes(2); + expect(mockHandler).toHaveBeenCalledWith(ElasticsearchConnectionStatus.Configured); + + mockHandler.mockClear(); + mockConnectionStatusClient.asInternalUser.ping.mockClear(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).not.toHaveBeenCalled(); + expect(mockHandler).not.toHaveBeenCalled(); + }); + + it('checks connection status only once if connection is known to be configured right from start', async () => { + mockConnectionStatusClient.asInternalUser.ping.mockResolvedValue( + interactiveSetupMock.createApiResponse({ body: true }) + ); + + const mockHandler = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler); + + jest.advanceTimersByTime(0); + await nextTick(); + + // Initial ping (connection error). + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith(ElasticsearchConnectionStatus.Configured); + + mockHandler.mockClear(); + mockConnectionStatusClient.asInternalUser.ping.mockClear(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).not.toHaveBeenCalled(); + expect(mockHandler).not.toHaveBeenCalled(); + + const mockHandler2 = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler2); + + // Source observable is complete, and handler should be called immediately. + expect(mockHandler2).toHaveBeenCalledTimes(1); + expect(mockHandler2).toHaveBeenCalledWith(ElasticsearchConnectionStatus.Configured); + + mockHandler2.mockClear(); + + // No status check should be made after the first attempt. + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).not.toHaveBeenCalled(); + expect(mockHandler).not.toHaveBeenCalled(); + expect(mockHandler2).not.toHaveBeenCalled(); + }); + + it('does not check connection status if there are no subscribers', async () => { + mockConnectionStatusClient.asInternalUser.ping.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + + const mockHandler = jest.fn(); + const mockSubscription = setupContract.connectionStatus$.subscribe(mockHandler); + + jest.advanceTimersByTime(0); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith(ElasticsearchConnectionStatus.NotConfigured); + + mockSubscription.unsubscribe(); + mockHandler.mockClear(); + mockConnectionStatusClient.asInternalUser.ping.mockClear(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).not.toHaveBeenCalled(); + expect(mockHandler).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).not.toHaveBeenCalled(); + expect(mockHandler).not.toHaveBeenCalled(); + }); + + it('treats non-connection errors the same as successful response', async () => { + mockConnectionStatusClient.asInternalUser.ping.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: {} }) + ) + ); + + const mockHandler = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler); + + jest.advanceTimersByTime(0); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith(ElasticsearchConnectionStatus.Configured); + + mockHandler.mockClear(); + mockConnectionStatusClient.asInternalUser.ping.mockClear(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).not.toHaveBeenCalled(); + expect(mockHandler).not.toHaveBeenCalled(); + }); + + it('treats product check error the same as successful response', async () => { + mockConnectionStatusClient.asInternalUser.ping.mockRejectedValue( + new errors.ProductNotSupportedError(interactiveSetupMock.createApiResponse({ body: {} })) + ); + + const mockHandler = jest.fn(); + setupContract.connectionStatus$.subscribe(mockHandler); + + jest.advanceTimersByTime(0); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith(ElasticsearchConnectionStatus.Configured); + + mockHandler.mockClear(); + mockConnectionStatusClient.asInternalUser.ping.mockClear(); + + jest.advanceTimersByTime(5000); + await nextTick(); + + expect(mockConnectionStatusClient.asInternalUser.ping).not.toHaveBeenCalled(); + expect(mockHandler).not.toHaveBeenCalled(); + }); + }); + + describe('#enroll()', () => { + it('fails if enroll call fails', async () => { + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.transport.request.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: { message: 'oh no' } }) + ) + ); + mockEnrollClient.asScoped.mockReturnValue(mockScopedClusterClient); + + await expect( + setupContract.enroll({ apiKey: 'apiKey', hosts: ['host1'] }) + ).rejects.toMatchInlineSnapshot(`[ResponseError: {"message":"oh no"}]`); + + expect(mockEnrollClient.asScoped).toHaveBeenCalledTimes(1); + expect(mockEnrollClient.close).toHaveBeenCalledTimes(1); + expect(mockAuthenticateClient.asInternalUser.security.authenticate).not.toHaveBeenCalled(); + }); + + it('fails if none of the hosts are accessible', async () => { + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.transport.request.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + mockEnrollClient.asScoped.mockReturnValue(mockScopedClusterClient); + + await expect( + setupContract.enroll({ apiKey: 'apiKey', hosts: ['host1', 'host2'] }) + ).rejects.toMatchInlineSnapshot(`[Error: Unable to connect to any of the provided hosts.]`); + + expect(mockEnrollClient.close).toHaveBeenCalledTimes(2); + expect(mockAuthenticateClient.asInternalUser.security.authenticate).not.toHaveBeenCalled(); + }); + + it('fails if authenticate call fails', async () => { + const mockEnrollScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockEnrollScopedClusterClient.asCurrentUser.transport.request.mockResolvedValue( + interactiveSetupMock.createApiResponse({ + statusCode: 200, + body: { token: { name: 'some-name', value: 'some-value' }, http_ca: 'some-ca' }, + }) + ); + mockEnrollClient.asScoped.mockReturnValue(mockEnrollScopedClusterClient); + + mockAuthenticateClient.asInternalUser.security.authenticate.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: { message: 'oh no' } }) + ) + ); + + await expect( + setupContract.enroll({ apiKey: 'apiKey', hosts: ['host1'] }) + ).rejects.toMatchInlineSnapshot(`[ResponseError: {"message":"oh no"}]`); + + expect(mockEnrollClient.asScoped).toHaveBeenCalledTimes(1); + expect(mockEnrollClient.close).toHaveBeenCalledTimes(1); + expect(mockAuthenticateClient.asInternalUser.security.authenticate).toHaveBeenCalledTimes( + 1 + ); + expect(mockAuthenticateClient.close).toHaveBeenCalledTimes(1); + }); + + it('iterates through all provided hosts until find an accessible one', async () => { + mockElasticsearchPreboot.createClient.mockClear(); + + const mockHostOneEnrollScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockHostOneEnrollScopedClusterClient.asCurrentUser.transport.request.mockRejectedValue( + new errors.ConnectionError( + 'some-message', + interactiveSetupMock.createApiResponse({ body: {} }) + ) + ); + + const mockHostTwoEnrollScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockHostTwoEnrollScopedClusterClient.asCurrentUser.transport.request.mockResolvedValue( + interactiveSetupMock.createApiResponse({ + statusCode: 200, + body: { + token: { name: 'some-name', value: 'some-value' }, + http_ca: '\n\nsome weird-ca_with\n content\n\n', + }, + }) + ); + + mockEnrollClient.asScoped + .mockReturnValueOnce(mockHostOneEnrollScopedClusterClient) + .mockReturnValueOnce(mockHostTwoEnrollScopedClusterClient); + + mockAuthenticateClient.asInternalUser.security.authenticate.mockResolvedValue( + interactiveSetupMock.createApiResponse({ statusCode: 200, body: {} as any }) + ); + + const expectedCa = `-----BEGIN CERTIFICATE----- + + +some weird+ca/with + + content + + +-----END CERTIFICATE----- +`; + + await expect( + setupContract.enroll({ apiKey: 'apiKey', hosts: ['host1', 'host2'] }) + ).resolves.toEqual({ + ca: expectedCa, + host: 'host2', + serviceAccountToken: { + name: 'some-name', + value: 'some-value', + }, + }); + + // Check that we created clients with the right parameters + expect(mockElasticsearchPreboot.createClient).toHaveBeenCalledTimes(3); + expect(mockElasticsearchPreboot.createClient).toHaveBeenCalledWith('enroll', { + hosts: ['host1'], + ssl: { verificationMode: 'none' }, + }); + expect(mockElasticsearchPreboot.createClient).toHaveBeenCalledWith('enroll', { + hosts: ['host2'], + ssl: { verificationMode: 'none' }, + }); + expect(mockElasticsearchPreboot.createClient).toHaveBeenCalledWith('authenticate', { + hosts: ['host2'], + serviceAccountToken: 'some-value', + ssl: { certificateAuthorities: [expectedCa] }, + }); + + // Check that we properly provided apiKeys to scoped clients. + expect(mockEnrollClient.asScoped).toHaveBeenCalledTimes(2); + expect(mockEnrollClient.asScoped).toHaveBeenNthCalledWith(1, { + headers: { authorization: 'ApiKey apiKey' }, + }); + expect(mockEnrollClient.asScoped).toHaveBeenNthCalledWith(2, { + headers: { authorization: 'ApiKey apiKey' }, + }); + + // Check that we properly called all required ES APIs. + expect( + mockHostOneEnrollScopedClusterClient.asCurrentUser.transport.request + ).toHaveBeenCalledTimes(1); + expect( + mockHostOneEnrollScopedClusterClient.asCurrentUser.transport.request + ).toHaveBeenCalledWith({ + method: 'GET', + path: '/_security/enroll/kibana', + }); + expect( + mockHostTwoEnrollScopedClusterClient.asCurrentUser.transport.request + ).toHaveBeenCalledTimes(1); + expect( + mockHostTwoEnrollScopedClusterClient.asCurrentUser.transport.request + ).toHaveBeenCalledWith({ + method: 'GET', + path: '/_security/enroll/kibana', + }); + expect(mockAuthenticateClient.asInternalUser.security.authenticate).toHaveBeenCalledTimes( + 1 + ); + + // Check that we properly closed all clients. + expect(mockEnrollClient.close).toHaveBeenCalledTimes(2); + expect(mockAuthenticateClient.close).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('#stop()', () => { + it('does not fail if called before `setup`', () => { + expect(() => service.stop()).not.toThrow(); + }); + + it('closes connection status check client', async () => { + const mockConnectionStatusClient = elasticsearchServiceMock.createCustomClusterClient(); + mockElasticsearchPreboot.createClient.mockImplementation((type) => { + switch (type) { + case 'ping': + return mockConnectionStatusClient; + default: + throw new Error(`Unexpected client type: ${type}`); + } + }); + + service.setup({ + elasticsearch: mockElasticsearchPreboot, + connectionCheckInterval: ConfigSchema.validate({}).connectionCheck.interval, + }); + service.stop(); + + expect(mockConnectionStatusClient.close).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.ts b/src/plugins/interactive_setup/server/elasticsearch_service.ts new file mode 100644 index 00000000000000..cad34e1a4d44ac --- /dev/null +++ b/src/plugins/interactive_setup/server/elasticsearch_service.ts @@ -0,0 +1,239 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ApiResponse } from '@elastic/elasticsearch'; +import { errors } from '@elastic/elasticsearch'; +import type { Duration } from 'moment'; +import type { Observable } from 'rxjs'; +import { from, of, timer } from 'rxjs'; +import { + catchError, + distinctUntilChanged, + exhaustMap, + map, + shareReplay, + takeWhile, +} from 'rxjs/operators'; + +import type { + ElasticsearchClientConfig, + ElasticsearchServicePreboot, + ICustomClusterClient, + Logger, + ScopeableRequest, +} from 'src/core/server'; + +import { ElasticsearchConnectionStatus } from '../common'; +import { getDetailedErrorMessage } from './errors'; + +interface EnrollParameters { + apiKey: string; + hosts: string[]; + // TODO: Integrate fingerprint check as soon core supports this new option: + // https://github.com/elastic/kibana/pull/108514 + caFingerprint?: string; +} + +export interface ElasticsearchServiceSetupDeps { + /** + * Core Elasticsearch service preboot contract; + */ + elasticsearch: ElasticsearchServicePreboot; + + /** + * Interval for the Elasticsearch connection check (whether it's configured or not). + */ + connectionCheckInterval: Duration; +} + +export interface ElasticsearchServiceSetup { + /** + * Observable that yields the last result of the Elasticsearch connection status check. + */ + connectionStatus$: Observable; + + /** + * Iterates through provided {@param hosts} one by one trying to call Kibana enrollment API using + * the specified {@param apiKey}. + * @param apiKey The ApiKey to use to authenticate Kibana enrollment request. + * @param hosts The list of Elasticsearch node addresses to enroll with. The addresses are supposed + * to point to exactly same Elasticsearch node, potentially available via different network interfaces. + */ + enroll: (params: EnrollParameters) => Promise; +} + +/** + * Result of the enrollment request. + */ +export interface EnrollResult { + /** + * Host address of the Elasticsearch node that successfully processed enrollment request. + */ + host: string; + /** + * PEM CA certificate for the Elasticsearch HTTP certificates. + */ + ca: string; + /** + * Service account token for the "elastic/kibana" service account. + */ + serviceAccountToken: { name: string; value: string }; +} + +export class ElasticsearchService { + /** + * Elasticsearch client used to check Elasticsearch connection status. + */ + private connectionStatusClient?: ICustomClusterClient; + constructor(private readonly logger: Logger) {} + + public setup({ + elasticsearch, + connectionCheckInterval, + }: ElasticsearchServiceSetupDeps): ElasticsearchServiceSetup { + const connectionStatusClient = (this.connectionStatusClient = elasticsearch.createClient( + 'ping' + )); + + return { + connectionStatus$: timer(0, connectionCheckInterval.asMilliseconds()).pipe( + exhaustMap(() => { + return from(connectionStatusClient.asInternalUser.ping()).pipe( + map(() => ElasticsearchConnectionStatus.Configured), + catchError((pingError) => + of( + pingError instanceof errors.ConnectionError + ? ElasticsearchConnectionStatus.NotConfigured + : ElasticsearchConnectionStatus.Configured + ) + ) + ); + }), + takeWhile( + (status) => status !== ElasticsearchConnectionStatus.Configured, + /* inclusive */ true + ), + distinctUntilChanged(), + shareReplay({ refCount: true, bufferSize: 1 }) + ), + enroll: this.enroll.bind(this, elasticsearch), + }; + } + + public stop() { + if (this.connectionStatusClient) { + this.connectionStatusClient.close().catch((err) => { + this.logger.debug(`Failed to stop Elasticsearch service: ${getDetailedErrorMessage(err)}`); + }); + this.connectionStatusClient = undefined; + } + } + + /** + * Iterates through provided {@param hosts} one by one trying to call Kibana enrollment API using + * the specified {@param apiKey}. + * @param elasticsearch Core Elasticsearch service preboot contract. + * @param apiKey The ApiKey to use to authenticate Kibana enrollment request. + * @param hosts The list of Elasticsearch node addresses to enroll with. The addresses are supposed + * to point to exactly same Elasticsearch node, potentially available via different network interfaces. + */ + private async enroll( + elasticsearch: ElasticsearchServicePreboot, + { apiKey, hosts }: EnrollParameters + ): Promise { + const scopeableRequest: ScopeableRequest = { headers: { authorization: `ApiKey ${apiKey}` } }; + const elasticsearchConfig: Partial = { + ssl: { verificationMode: 'none' }, + }; + + // We should iterate through all provided hosts until we find an accessible one. + for (const host of hosts) { + this.logger.debug(`Trying to enroll with "${host}" host`); + const enrollClient = elasticsearch.createClient('enroll', { + ...elasticsearchConfig, + hosts: [host], + }); + + let enrollmentResponse; + try { + enrollmentResponse = (await enrollClient + .asScoped(scopeableRequest) + .asCurrentUser.transport.request({ + method: 'GET', + path: '/_security/enroll/kibana', + })) as ApiResponse<{ token: { name: string; value: string }; http_ca: string }>; + } catch (err) { + // We expect that all hosts belong to exactly same node and any non-connection error for one host would mean + // that enrollment will fail for any other host and we should bail out. + if (err instanceof errors.ConnectionError || err instanceof errors.TimeoutError) { + this.logger.error( + `Unable to connect to "${host}" host, will proceed to the next host if available: ${getDetailedErrorMessage( + err + )}` + ); + continue; + } + + this.logger.error(`Failed to enroll with "${host}" host: ${getDetailedErrorMessage(err)}`); + throw err; + } finally { + await enrollClient.close(); + } + + this.logger.debug( + `Successfully enrolled with "${host}" host, token name: ${enrollmentResponse.body.token.name}, CA certificate: ${enrollmentResponse.body.http_ca}` + ); + + const enrollResult = { + host, + ca: ElasticsearchService.createPemCertificate(enrollmentResponse.body.http_ca), + serviceAccountToken: enrollmentResponse.body.token, + }; + + // Now try to use retrieved password and CA certificate to authenticate to this host. + const authenticateClient = elasticsearch.createClient('authenticate', { + hosts: [host], + serviceAccountToken: enrollResult.serviceAccountToken.value, + ssl: { certificateAuthorities: [enrollResult.ca] }, + }); + + this.logger.debug( + `Verifying if "${enrollmentResponse.body.token.name}" token can authenticate to "${host}" host.` + ); + + try { + await authenticateClient.asInternalUser.security.authenticate(); + this.logger.debug( + `Successfully authenticated "${enrollmentResponse.body.token.name}" token to "${host}" host.` + ); + } catch (err) { + this.logger.error( + `Failed to authenticate "${ + enrollmentResponse.body.token.name + }" token to "${host}" host: ${getDetailedErrorMessage(err)}.` + ); + throw err; + } finally { + await authenticateClient.close(); + } + + return enrollResult; + } + + throw new Error('Unable to connect to any of the provided hosts.'); + } + + private static createPemCertificate(derCaString: string) { + // Use `X509Certificate` class once we upgrade to Node v16. + return `-----BEGIN CERTIFICATE-----\n${derCaString + .replace(/_/g, '/') + .replace(/-/g, '+') + .replace(/([^\n]{1,65})/g, '$1\n') + .replace(/\n$/g, '')}\n-----END CERTIFICATE-----\n`; + } +} diff --git a/src/plugins/interactive_setup/server/errors.test.ts b/src/plugins/interactive_setup/server/errors.test.ts new file mode 100644 index 00000000000000..e9ef64fb0d3d7e --- /dev/null +++ b/src/plugins/interactive_setup/server/errors.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors as esErrors } from '@elastic/elasticsearch'; + +import * as errors from './errors'; +import { interactiveSetupMock } from './mocks'; + +describe('errors', () => { + describe('#getErrorStatusCode', () => { + it('extracts status code from Elasticsearch client response error', () => { + expect( + errors.getErrorStatusCode( + new esErrors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 400, body: {} }) + ) + ) + ).toBe(400); + expect( + errors.getErrorStatusCode( + new esErrors.ResponseError( + interactiveSetupMock.createApiResponse({ statusCode: 401, body: {} }) + ) + ) + ).toBe(401); + }); + + it('extracts status code from `status` property', () => { + expect(errors.getErrorStatusCode({ statusText: 'Bad Request', status: 400 })).toBe(400); + expect(errors.getErrorStatusCode({ statusText: 'Unauthorized', status: 401 })).toBe(401); + }); + }); + + describe('#getDetailedErrorMessage', () => { + it('extracts body from Elasticsearch client response error', () => { + expect( + errors.getDetailedErrorMessage( + new esErrors.ResponseError( + interactiveSetupMock.createApiResponse({ + statusCode: 401, + body: { field1: 'value-1', field2: 'value-2' }, + }) + ) + ) + ).toBe(JSON.stringify({ field1: 'value-1', field2: 'value-2' })); + }); + + it('extracts `message` property', () => { + expect(errors.getDetailedErrorMessage(new Error('some-message'))).toBe('some-message'); + }); + }); +}); diff --git a/src/plugins/interactive_setup/server/errors.ts b/src/plugins/interactive_setup/server/errors.ts new file mode 100644 index 00000000000000..5f1d2388b3938f --- /dev/null +++ b/src/plugins/interactive_setup/server/errors.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors } from '@elastic/elasticsearch'; + +/** + * Extracts error code from Boom and Elasticsearch "native" errors. + * @param error Error instance to extract status code from. + */ +export function getErrorStatusCode(error: any): number { + if (error instanceof errors.ResponseError) { + return error.statusCode; + } + + return error.statusCode || error.status; +} + +/** + * Extracts detailed error message from Boom and Elasticsearch "native" errors. It's supposed to be + * only logged on the server side and never returned to the client as it may contain sensitive + * information. + * @param error Error instance to extract message from. + */ +export function getDetailedErrorMessage(error: any): string { + if (error instanceof errors.ResponseError) { + return JSON.stringify(error.body); + } + + return error.message; +} diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.mock.ts b/src/plugins/interactive_setup/server/kibana_config_writer.mock.ts new file mode 100644 index 00000000000000..d2c498e5fc0773 --- /dev/null +++ b/src/plugins/interactive_setup/server/kibana_config_writer.mock.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PublicMethodsOf } from '@kbn/utility-types'; + +import type { KibanaConfigWriter } from './kibana_config_writer'; + +export const kibanaConfigWriterMock = { + create: (): jest.Mocked> => ({ + isConfigWritable: jest.fn().mockResolvedValue(true), + writeConfig: jest.fn().mockResolvedValue(undefined), + }), +}; diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.test.ts b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts new file mode 100644 index 00000000000000..7ae98157ba1563 --- /dev/null +++ b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +jest.mock('fs/promises'); +import { constants } from 'fs'; + +import { loggingSystemMock } from 'src/core/server/mocks'; + +import { KibanaConfigWriter } from './kibana_config_writer'; + +describe('KibanaConfigWriter', () => { + let mockFsAccess: jest.Mock; + let mockWriteFile: jest.Mock; + let mockAppendFile: jest.Mock; + let kibanaConfigWriter: KibanaConfigWriter; + beforeEach(() => { + jest.spyOn(Date, 'now').mockReturnValue(1234); + + const fsMocks = jest.requireMock('fs/promises'); + mockFsAccess = fsMocks.access; + mockWriteFile = fsMocks.writeFile; + mockAppendFile = fsMocks.appendFile; + + kibanaConfigWriter = new KibanaConfigWriter( + '/some/path/kibana.yml', + loggingSystemMock.createLogger() + ); + }); + + afterEach(() => jest.resetAllMocks()); + + describe('#isConfigWritable()', () => { + it('returns `false` if config directory is not writable even if kibana yml is writable', async () => { + mockFsAccess.mockImplementation((path, modifier) => + path === '/some/path' && modifier === constants.W_OK ? Promise.reject() : Promise.resolve() + ); + + await expect(kibanaConfigWriter.isConfigWritable()).resolves.toBe(false); + }); + + it('returns `false` if kibana yml is NOT writable if even config directory is writable', async () => { + mockFsAccess.mockImplementation((path, modifier) => + path === '/some/path/kibana.yml' && modifier === constants.W_OK + ? Promise.reject() + : Promise.resolve() + ); + + await expect(kibanaConfigWriter.isConfigWritable()).resolves.toBe(false); + }); + + it('returns `true` if both kibana yml and config directory are writable', async () => { + mockFsAccess.mockResolvedValue(undefined); + + await expect(kibanaConfigWriter.isConfigWritable()).resolves.toBe(true); + }); + + it('returns `true` even if kibana yml does not exist when config directory is writable', async () => { + mockFsAccess.mockImplementation((path) => + path === '/some/path/kibana.yml' ? Promise.reject() : Promise.resolve() + ); + + await expect(kibanaConfigWriter.isConfigWritable()).resolves.toBe(true); + }); + }); + + describe('#writeConfig()', () => { + it('throws if cannot write CA file', async () => { + mockWriteFile.mockRejectedValue(new Error('Oh no!')); + + await expect( + kibanaConfigWriter.writeConfig({ + ca: 'ca-content', + host: '', + serviceAccountToken: { name: '', value: '' }, + }) + ).rejects.toMatchInlineSnapshot(`[Error: Oh no!]`); + + expect(mockWriteFile).toHaveBeenCalledTimes(1); + expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content'); + expect(mockAppendFile).not.toHaveBeenCalled(); + }); + + it('throws if cannot append config to yaml file', async () => { + mockAppendFile.mockRejectedValue(new Error('Oh no!')); + + await expect( + kibanaConfigWriter.writeConfig({ + ca: 'ca-content', + host: 'some-host', + serviceAccountToken: { name: 'some-token', value: 'some-value' }, + }) + ).rejects.toMatchInlineSnapshot(`[Error: Oh no!]`); + + expect(mockWriteFile).toHaveBeenCalledTimes(1); + expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content'); + expect(mockAppendFile).toHaveBeenCalledTimes(1); + expect(mockAppendFile).toHaveBeenCalledWith( + '/some/path/kibana.yml', + ` + +# This section was automatically generated during setup (service account token name is "some-token"). +elasticsearch.hosts: [some-host] +elasticsearch.serviceAccountToken: some-value +elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt] + +` + ); + }); + + it('can successfully write CA certificate and elasticsearch config to the disk', async () => { + await expect( + kibanaConfigWriter.writeConfig({ + ca: 'ca-content', + host: 'some-host', + serviceAccountToken: { name: 'some-token', value: 'some-value' }, + }) + ).resolves.toBeUndefined(); + + expect(mockWriteFile).toHaveBeenCalledTimes(1); + expect(mockWriteFile).toHaveBeenCalledWith('/some/path/ca_1234.crt', 'ca-content'); + expect(mockAppendFile).toHaveBeenCalledTimes(1); + expect(mockAppendFile).toHaveBeenCalledWith( + '/some/path/kibana.yml', + ` + +# This section was automatically generated during setup (service account token name is "some-token"). +elasticsearch.hosts: [some-host] +elasticsearch.serviceAccountToken: some-value +elasticsearch.ssl.certificateAuthorities: [/some/path/ca_1234.crt] + +` + ); + }); + }); +}); diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.ts b/src/plugins/interactive_setup/server/kibana_config_writer.ts new file mode 100644 index 00000000000000..b3178d9a909bd3 --- /dev/null +++ b/src/plugins/interactive_setup/server/kibana_config_writer.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { constants } from 'fs'; +import fs from 'fs/promises'; +import yaml from 'js-yaml'; +import path from 'path'; + +import type { Logger } from 'src/core/server'; + +import { getDetailedErrorMessage } from './errors'; + +export interface WriteConfigParameters { + host: string; + ca: string; + serviceAccountToken: { name: string; value: string }; +} + +export class KibanaConfigWriter { + constructor(private readonly configPath: string, private readonly logger: Logger) {} + + /** + * Checks if we can write to the Kibana configuration file and configuration directory. + */ + public async isConfigWritable() { + try { + // We perform two separate checks here: + // 1. If we can write to config directory to add a new CA certificate file and potentially Kibana configuration + // file if it doesn't exist for some reason. + // 2. If we can write to the Kibana configuration file if it exists. + const canWriteToConfigDirectory = fs.access(path.dirname(this.configPath), constants.W_OK); + await Promise.all([ + canWriteToConfigDirectory, + fs.access(this.configPath, constants.F_OK).then( + () => fs.access(this.configPath, constants.W_OK), + () => canWriteToConfigDirectory + ), + ]); + return true; + } catch { + return false; + } + } + + /** + * Writes Elasticsearch configuration to the disk. + * @param params + */ + public async writeConfig(params: WriteConfigParameters) { + const caPath = path.join(path.dirname(this.configPath), `ca_${Date.now()}.crt`); + + this.logger.debug(`Writing CA certificate to ${caPath}.`); + try { + await fs.writeFile(caPath, params.ca); + this.logger.debug(`Successfully wrote CA certificate to ${caPath}.`); + } catch (err) { + this.logger.error( + `Failed to write CA certificate to ${caPath}: ${getDetailedErrorMessage(err)}.` + ); + throw err; + } + + this.logger.debug(`Writing Elasticsearch configuration to ${this.configPath}.`); + try { + await fs.appendFile( + this.configPath, + `\n\n# This section was automatically generated during setup (service account token name is "${ + params.serviceAccountToken.name + }").\n${yaml.safeDump( + { + 'elasticsearch.hosts': [params.host], + 'elasticsearch.serviceAccountToken': params.serviceAccountToken.value, + 'elasticsearch.ssl.certificateAuthorities': [caPath], + }, + { flowLevel: 1 } + )}\n` + ); + this.logger.debug(`Successfully wrote Elasticsearch configuration to ${this.configPath}.`); + } catch (err) { + this.logger.error( + `Failed to write Elasticsearch configuration to ${ + this.configPath + }: ${getDetailedErrorMessage(err)}.` + ); + throw err; + } + } +} diff --git a/src/plugins/interactive_setup/server/mocks.ts b/src/plugins/interactive_setup/server/mocks.ts new file mode 100644 index 00000000000000..75b28a502b6d4d --- /dev/null +++ b/src/plugins/interactive_setup/server/mocks.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ApiResponse } from '@elastic/elasticsearch'; + +function createApiResponseMock( + apiResponse: Pick, 'body'> & + Partial, 'body'>> +): ApiResponse { + return { + statusCode: null, + headers: null, + warnings: null, + meta: {} as any, + ...apiResponse, + }; +} + +export const interactiveSetupMock = { + createApiResponse: createApiResponseMock, +}; diff --git a/src/plugins/interactive_setup/server/plugin.ts b/src/plugins/interactive_setup/server/plugin.ts index 6b2a12bad76bc0..06ece32ba9c4e3 100644 --- a/src/plugins/interactive_setup/server/plugin.ts +++ b/src/plugins/interactive_setup/server/plugin.ts @@ -13,11 +13,18 @@ import type { CorePreboot, Logger, PluginInitializerContext, PrebootPlugin } fro import { ElasticsearchConnectionStatus } from '../common'; import type { ConfigSchema, ConfigType } from './config'; +import { ElasticsearchService } from './elasticsearch_service'; +import { KibanaConfigWriter } from './kibana_config_writer'; import { defineRoutes } from './routes'; export class UserSetupPlugin implements PrebootPlugin { readonly #logger: Logger; + #elasticsearchConnectionStatusSubscription?: Subscription; + readonly #elasticsearch = new ElasticsearchService( + this.initializerContext.logger.get('elasticsearch') + ); + #configSubscription?: Subscription; #config?: ConfigType; readonly #getConfig = () => { @@ -27,11 +34,6 @@ export class UserSetupPlugin implements PrebootPlugin { return this.#config; }; - #elasticsearchConnectionStatus = ElasticsearchConnectionStatus.Unknown; - readonly #getElasticsearchConnectionStatus = () => { - return this.#elasticsearchConnectionStatus; - }; - constructor(private readonly initializerContext: PluginInitializerContext) { this.#logger = this.initializerContext.logger.get(); } @@ -65,45 +67,48 @@ export class UserSetupPlugin implements PrebootPlugin { }) ); - // If preliminary check above indicates that user didn't alter default Elasticsearch connection - // details, it doesn't mean Elasticsearch connection isn't configured. There is a chance that they - // already disabled security features in Elasticsearch and everything should work by default. - // We should check if we can connect to Elasticsearch with default configuration to know if we - // need to activate interactive setup. This check can take some time, so we should register our - // routes to let interactive setup UI to handle user requests until the check is complete. - core.elasticsearch - .createClient('ping') - .asInternalUser.ping() - .then( - (pingResponse) => { - if (pingResponse.body) { - this.#logger.debug( - 'Kibana is already properly configured to connect to Elasticsearch. Interactive setup mode will not be activated.' - ); - this.#elasticsearchConnectionStatus = ElasticsearchConnectionStatus.Configured; - completeSetup({ shouldReloadConfig: false }); - } else { - this.#logger.debug( - 'Kibana is not properly configured to connect to Elasticsearch. Interactive setup mode will be activated.' - ); - this.#elasticsearchConnectionStatus = ElasticsearchConnectionStatus.NotConfigured; - } - }, - () => { - // TODO: we should probably react differently to different errors. 401 - credentials aren't correct, etc. - // Do we want to constantly ping ES if interactive mode UI isn't active? Just in case user runs Kibana and then - // configure Elasticsearch so that it can eventually connect to it without any configuration changes? - this.#elasticsearchConnectionStatus = ElasticsearchConnectionStatus.NotConfigured; + // If preliminary checks above indicate that user didn't alter default Elasticsearch connection + // details, it doesn't mean Elasticsearch connection isn't configured. There is a chance that + // user has already disabled security features in Elasticsearch and everything should work by + // default. We should check if we can connect to Elasticsearch with default configuration to + // know if we need to activate interactive setup. This check can take some time, so we should + // register our routes to let interactive setup UI to handle user requests until the check is + // complete. Moreover Elasticsearch may be just temporarily unavailable and we should poll its + // status until we can connect or use configures connection via interactive setup mode. + const elasticsearch = this.#elasticsearch.setup({ + elasticsearch: core.elasticsearch, + connectionCheckInterval: this.#getConfig().connectionCheck.interval, + }); + this.#elasticsearchConnectionStatusSubscription = elasticsearch.connectionStatus$.subscribe( + (status) => { + if (status === ElasticsearchConnectionStatus.Configured) { + this.#logger.debug( + 'Skipping interactive setup mode since Kibana is already properly configured to connect to Elasticsearch at http://localhost:9200.' + ); + completeSetup({ shouldReloadConfig: false }); + } else { + this.#logger.debug( + 'Starting interactive setup mode since Kibana cannot to connect to Elasticsearch at http://localhost:9200.' + ); } - ); + } + ); + + // If possible, try to use `*.dev.yml` config when Kibana is run in development mode. + const configPath = this.initializerContext.env.mode.dev + ? this.initializerContext.env.configs.find((config) => config.endsWith('.dev.yml')) ?? + this.initializerContext.env.configs[0] + : this.initializerContext.env.configs[0]; core.http.registerRoutes('', (router) => { defineRoutes({ router, basePath: core.http.basePath, logger: this.#logger.get('routes'), + preboot: { ...core.preboot, completeSetup }, + kibanaConfigWriter: new KibanaConfigWriter(configPath, this.#logger.get('kibana-config')), + elasticsearch, getConfig: this.#getConfig.bind(this), - getElasticsearchConnectionStatus: this.#getElasticsearchConnectionStatus.bind(this), }); }); } @@ -115,5 +120,12 @@ export class UserSetupPlugin implements PrebootPlugin { this.#configSubscription.unsubscribe(); this.#configSubscription = undefined; } + + if (this.#elasticsearchConnectionStatusSubscription) { + this.#elasticsearchConnectionStatusSubscription.unsubscribe(); + this.#elasticsearchConnectionStatusSubscription = undefined; + } + + this.#elasticsearch.stop(); } } diff --git a/src/plugins/interactive_setup/server/routes/enroll.test.ts b/src/plugins/interactive_setup/server/routes/enroll.test.ts new file mode 100644 index 00000000000000..4fc91e52524804 --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/enroll.test.ts @@ -0,0 +1,305 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors } from '@elastic/elasticsearch'; + +import type { ObjectType } from '@kbn/config-schema'; +import type { IRouter, RequestHandler, RequestHandlerContext, RouteConfig } from 'src/core/server'; +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { ElasticsearchConnectionStatus } from '../../common'; +import { interactiveSetupMock } from '../mocks'; +import { defineEnrollRoutes } from './enroll'; +import { routeDefinitionParamsMock } from './index.mock'; + +describe('Enroll routes', () => { + let router: jest.Mocked; + let mockRouteParams: ReturnType; + let mockContext: RequestHandlerContext; + beforeEach(() => { + mockRouteParams = routeDefinitionParamsMock.create(); + router = mockRouteParams.router; + + mockContext = ({} as unknown) as RequestHandlerContext; + + defineEnrollRoutes(mockRouteParams); + }); + + describe('#enroll', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + + beforeEach(() => { + const [enrollRouteConfig, enrollRouteHandler] = router.post.mock.calls.find( + ([{ path }]) => path === '/internal/interactive_setup/enroll' + )!; + + routeConfig = enrollRouteConfig; + routeHandler = enrollRouteHandler; + }); + + it('correctly defines route.', () => { + expect(routeConfig.options).toEqual({ authRequired: false }); + + const bodySchema = (routeConfig.validate as any).body as ObjectType; + expect(() => bodySchema.validate({})).toThrowErrorMatchingInlineSnapshot( + `"[hosts]: expected value of type [array] but got [undefined]"` + ); + + expect(() => bodySchema.validate({ hosts: [] })).toThrowErrorMatchingInlineSnapshot( + `"[hosts]: array size is [0], but cannot be smaller than [1]"` + ); + expect(() => + bodySchema.validate({ hosts: ['localhost:9200'] }) + ).toThrowErrorMatchingInlineSnapshot(`"[hosts.0]: expected URI with scheme [https]."`); + expect(() => + bodySchema.validate({ hosts: ['http://localhost:9200'] }) + ).toThrowErrorMatchingInlineSnapshot(`"[hosts.0]: expected URI with scheme [https]."`); + expect(() => + bodySchema.validate({ + apiKey: 'some-key', + hosts: ['https://localhost:9200', 'http://localhost:9243'], + }) + ).toThrowErrorMatchingInlineSnapshot(`"[hosts.1]: expected URI with scheme [https]."`); + + expect(() => + bodySchema.validate({ hosts: ['https://localhost:9200'] }) + ).toThrowErrorMatchingInlineSnapshot( + `"[apiKey]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodySchema.validate({ apiKey: '', hosts: ['https://localhost:9200'] }) + ).toThrowErrorMatchingInlineSnapshot( + `"[apiKey]: value has length [0] but it must have a minimum length of [1]."` + ); + + expect(() => + bodySchema.validate({ apiKey: 'some-key', hosts: ['https://localhost:9200'] }) + ).toThrowErrorMatchingInlineSnapshot( + `"[caFingerprint]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodySchema.validate({ + apiKey: 'some-key', + hosts: ['https://localhost:9200'], + caFingerprint: '12345', + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[caFingerprint]: value has length [5] but it must have a minimum length of [64]."` + ); + + expect( + bodySchema.validate( + bodySchema.validate({ + apiKey: 'some-key', + hosts: ['https://localhost:9200'], + caFingerprint: 'a'.repeat(64), + }) + ) + ).toEqual({ + apiKey: 'some-key', + hosts: ['https://localhost:9200'], + caFingerprint: 'a'.repeat(64), + }); + }); + + it('fails if setup is not on hold.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(false); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { apiKey: 'some-key', hosts: ['host1', 'host2'], caFingerprint: 'ab:cd:ef' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 400, + options: { body: 'Cannot process request outside of preboot stage.' }, + payload: 'Cannot process request outside of preboot stage.', + }); + + expect(mockRouteParams.elasticsearch.enroll).not.toHaveBeenCalled(); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if Elasticsearch connection is already configured.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.Configured + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { apiKey: 'some-key', hosts: ['host1', 'host2'], caFingerprint: 'ab:cd:ef' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 400, + options: { + body: { + message: 'Elasticsearch connection is already configured.', + attributes: { type: 'elasticsearch_connection_configured' }, + }, + }, + payload: { + message: 'Elasticsearch connection is already configured.', + attributes: { type: 'elasticsearch_connection_configured' }, + }, + }); + + expect(mockRouteParams.elasticsearch.enroll).not.toHaveBeenCalled(); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if Kibana config is not writable.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(false); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { apiKey: 'some-key', hosts: ['host1', 'host2'], caFingerprint: 'ab:cd:ef' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 500, + options: { + body: { + message: 'Kibana process does not have enough permissions to write to config file.', + attributes: { type: 'kibana_config_not_writable' }, + }, + statusCode: 500, + }, + payload: { + message: 'Kibana process does not have enough permissions to write to config file.', + attributes: { type: 'kibana_config_not_writable' }, + }, + }); + + expect(mockRouteParams.elasticsearch.enroll).not.toHaveBeenCalled(); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if enroll call fails.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(true); + mockRouteParams.elasticsearch.enroll.mockRejectedValue( + new errors.ResponseError( + interactiveSetupMock.createApiResponse({ + statusCode: 401, + body: { message: 'some-secret-message' }, + }) + ) + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { apiKey: 'some-key', hosts: ['host1', 'host2'], caFingerprint: 'ab:cd:ef' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 500, + options: { + body: { message: 'Failed to enroll.', attributes: { type: 'enroll_failure' } }, + statusCode: 500, + }, + payload: { message: 'Failed to enroll.', attributes: { type: 'enroll_failure' } }, + }); + + expect(mockRouteParams.elasticsearch.enroll).toHaveBeenCalledTimes(1); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).not.toHaveBeenCalled(); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('fails if cannot write configuration to the disk.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(true); + mockRouteParams.elasticsearch.enroll.mockResolvedValue({ + ca: 'some-ca', + host: 'host', + serviceAccountToken: { name: 'some-name', value: 'some-value' }, + }); + mockRouteParams.kibanaConfigWriter.writeConfig.mockRejectedValue( + new Error('Some error with sensitive path') + ); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { apiKey: 'some-key', hosts: ['host1', 'host2'], caFingerprint: 'ab:cd:ef' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 500, + options: { + body: { + message: 'Failed to save configuration.', + attributes: { type: 'kibana_config_failure' }, + }, + statusCode: 500, + }, + payload: { + message: 'Failed to save configuration.', + attributes: { type: 'kibana_config_failure' }, + }, + }); + + expect(mockRouteParams.elasticsearch.enroll).toHaveBeenCalledTimes(1); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).toHaveBeenCalledTimes(1); + expect(mockRouteParams.preboot.completeSetup).not.toHaveBeenCalled(); + }); + + it('can successfully enrol and save configuration to the disk.', async () => { + mockRouteParams.preboot.isSetupOnHold.mockReturnValue(true); + mockRouteParams.elasticsearch.connectionStatus$.next( + ElasticsearchConnectionStatus.NotConfigured + ); + mockRouteParams.kibanaConfigWriter.isConfigWritable.mockResolvedValue(true); + mockRouteParams.elasticsearch.enroll.mockResolvedValue({ + ca: 'some-ca', + host: 'host', + serviceAccountToken: { name: 'some-name', value: 'some-value' }, + }); + mockRouteParams.kibanaConfigWriter.writeConfig.mockResolvedValue(); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { apiKey: 'some-key', hosts: ['host1', 'host2'], caFingerprint: 'ab:cd:ef' }, + }); + + await expect(routeHandler(mockContext, mockRequest, kibanaResponseFactory)).resolves.toEqual({ + status: 204, + options: {}, + payload: undefined, + }); + + expect(mockRouteParams.elasticsearch.enroll).toHaveBeenCalledTimes(1); + expect(mockRouteParams.elasticsearch.enroll).toHaveBeenCalledWith({ + apiKey: 'some-key', + hosts: ['host1', 'host2'], + caFingerprint: 'ab:cd:ef', + }); + + expect(mockRouteParams.kibanaConfigWriter.writeConfig).toHaveBeenCalledTimes(1); + expect(mockRouteParams.kibanaConfigWriter.writeConfig).toHaveBeenCalledWith({ + ca: 'some-ca', + host: 'host', + serviceAccountToken: { name: 'some-name', value: 'some-value' }, + }); + + expect(mockRouteParams.preboot.completeSetup).toHaveBeenCalledTimes(1); + expect(mockRouteParams.preboot.completeSetup).toHaveBeenCalledWith({ + shouldReloadConfig: true, + }); + }); + }); +}); diff --git a/src/plugins/interactive_setup/server/routes/enroll.ts b/src/plugins/interactive_setup/server/routes/enroll.ts index a600d181097605..91b391bf8b109a 100644 --- a/src/plugins/interactive_setup/server/routes/enroll.ts +++ b/src/plugins/interactive_setup/server/routes/enroll.ts @@ -6,26 +6,105 @@ * Side Public License, v 1. */ +import { first } from 'rxjs/operators'; + import { schema } from '@kbn/config-schema'; +import { ElasticsearchConnectionStatus } from '../../common'; +import type { EnrollResult } from '../elasticsearch_service'; import type { RouteDefinitionParams } from './'; /** * Defines routes to deal with Elasticsearch `enroll_kibana` APIs. */ -export function defineEnrollRoutes({ router }: RouteDefinitionParams) { +export function defineEnrollRoutes({ + router, + logger, + kibanaConfigWriter, + elasticsearch, + preboot, +}: RouteDefinitionParams) { router.post( { path: '/internal/interactive_setup/enroll', validate: { - body: schema.object({ token: schema.string() }), + body: schema.object({ + hosts: schema.arrayOf(schema.uri({ scheme: 'https' }), { + minSize: 1, + }), + apiKey: schema.string({ minLength: 1 }), + caFingerprint: schema.string({ maxLength: 64, minLength: 64 }), + }), }, options: { authRequired: false }, }, async (context, request, response) => { - return response.forbidden({ - body: { message: `API is not implemented yet.` }, - }); + if (!preboot.isSetupOnHold()) { + logger.error(`Invalid request to [path=${request.url.pathname}] outside of preboot stage`); + return response.badRequest({ body: 'Cannot process request outside of preboot stage.' }); + } + + const connectionStatus = await elasticsearch.connectionStatus$.pipe(first()).toPromise(); + if (connectionStatus === ElasticsearchConnectionStatus.Configured) { + logger.error( + `Invalid request to [path=${request.url.pathname}], Elasticsearch connection is already configured.` + ); + return response.badRequest({ + body: { + message: 'Elasticsearch connection is already configured.', + attributes: { type: 'elasticsearch_connection_configured' }, + }, + }); + } + + // The most probable misconfiguration case is when Kibana process isn't allowed to write to the + // Kibana configuration file. We'll still have to handle possible filesystem access errors + // when we actually write to the disk, but this preliminary check helps us to avoid unnecessary + // enrollment call and communicate that to the user early. + const isConfigWritable = await kibanaConfigWriter.isConfigWritable(); + if (!isConfigWritable) { + logger.error('Kibana process does not have enough permissions to write to config file'); + return response.customError({ + statusCode: 500, + body: { + message: 'Kibana process does not have enough permissions to write to config file.', + attributes: { type: 'kibana_config_not_writable' }, + }, + }); + } + + let enrollResult: EnrollResult; + try { + enrollResult = await elasticsearch.enroll({ + apiKey: request.body.apiKey, + hosts: request.body.hosts, + caFingerprint: request.body.caFingerprint, + }); + } catch { + // For security reasons, we shouldn't leak to the user whether Elasticsearch node couldn't process enrollment + // request or we just couldn't connect to any of the provided hosts. + return response.customError({ + statusCode: 500, + body: { message: 'Failed to enroll.', attributes: { type: 'enroll_failure' } }, + }); + } + + try { + await kibanaConfigWriter.writeConfig(enrollResult); + } catch { + // For security reasons, we shouldn't leak any filesystem related errors. + return response.customError({ + statusCode: 500, + body: { + message: 'Failed to save configuration.', + attributes: { type: 'kibana_config_failure' }, + }, + }); + } + + preboot.completeSetup({ shouldReloadConfig: true }); + + return response.noContent(); } ); } diff --git a/src/plugins/interactive_setup/server/routes/index.mock.ts b/src/plugins/interactive_setup/server/routes/index.mock.ts new file mode 100644 index 00000000000000..249d1277269e7d --- /dev/null +++ b/src/plugins/interactive_setup/server/routes/index.mock.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { coreMock, httpServiceMock, loggingSystemMock } from 'src/core/server/mocks'; + +import { ConfigSchema } from '../config'; +import { elasticsearchServiceMock } from '../elasticsearch_service.mock'; +import { kibanaConfigWriterMock } from '../kibana_config_writer.mock'; + +export const routeDefinitionParamsMock = { + create: (config: Record = {}) => ({ + router: httpServiceMock.createRouter(), + basePath: httpServiceMock.createBasePath(), + csp: httpServiceMock.createSetupContract().csp, + logger: loggingSystemMock.create().get(), + preboot: { ...coreMock.createPreboot().preboot, completeSetup: jest.fn() }, + getConfig: jest.fn().mockReturnValue(ConfigSchema.validate(config)), + elasticsearch: elasticsearchServiceMock.createSetup(), + kibanaConfigWriter: kibanaConfigWriterMock.create(), + }), +}; diff --git a/src/plugins/interactive_setup/server/routes/index.ts b/src/plugins/interactive_setup/server/routes/index.ts index 0f14f5ffac8ece..752c5828ecb59f 100644 --- a/src/plugins/interactive_setup/server/routes/index.ts +++ b/src/plugins/interactive_setup/server/routes/index.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ -import type { IBasePath, IRouter, Logger } from 'src/core/server'; +import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { IBasePath, IRouter, Logger, PrebootServicePreboot } from 'src/core/server'; -import type { ElasticsearchConnectionStatus } from '../../common'; import type { ConfigType } from '../config'; +import type { ElasticsearchServiceSetup } from '../elasticsearch_service'; +import type { KibanaConfigWriter } from '../kibana_config_writer'; import { defineEnrollRoutes } from './enroll'; /** @@ -19,8 +21,12 @@ export interface RouteDefinitionParams { readonly router: IRouter; readonly basePath: IBasePath; readonly logger: Logger; + readonly preboot: PrebootServicePreboot & { + completeSetup: (result: { shouldReloadConfig: boolean }) => void; + }; + readonly kibanaConfigWriter: PublicMethodsOf; + readonly elasticsearch: ElasticsearchServiceSetup; readonly getConfig: () => ConfigType; - readonly getElasticsearchConnectionStatus: () => ElasticsearchConnectionStatus; } export function defineRoutes(params: RouteDefinitionParams) { From 4419efcbd0b750cd8b5432229ad229f2c5a2ceb1 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 19 Aug 2021 12:36:23 +0300 Subject: [PATCH 35/36] [XY, Pie] Long legend values support (#108365) * [XY, Pie] Long legend values support * Update vislib snapshots * Fix truncate labels to work only for slice labels positioned outside the chart * Address PR comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/components/options/index.ts | 1 + .../options/long_legend_options.test.tsx | 48 +++++++++++++ .../options/long_legend_options.tsx | 71 +++++++++++++++++++ .../public/__snapshots__/pie_fn.test.ts.snap | 2 + .../public/editor/components/pie.test.tsx | 14 ++++ .../public/editor/components/pie.tsx | 15 +++- .../editor/components/truncate_labels.tsx | 12 +++- src/plugins/vis_type_pie/public/mocks.ts | 2 + .../vis_type_pie/public/pie_component.tsx | 11 ++- .../vis_type_pie/public/pie_fn.test.ts | 2 + src/plugins/vis_type_pie/public/pie_fn.ts | 13 ++++ .../public/sample_vis.test.mocks.ts | 2 + src/plugins/vis_type_pie/public/to_ast.ts | 2 + .../vis_type_pie/public/types/types.ts | 2 + .../public/utils/get_columns.test.ts | 2 + .../vis_type_pie/public/utils/get_config.ts | 1 + .../vis_type_pie/public/utils/get_layers.ts | 7 +- .../vis_type_pie/public/vis_type/pie.ts | 2 + .../public/__snapshots__/to_ast.test.ts.snap | 2 +- .../public/__snapshots__/to_ast.test.ts.snap | 6 ++ .../public/components/xy_settings.tsx | 7 ++ .../point_series/point_series.mocks.ts | 4 ++ .../point_series/point_series.test.tsx | 20 ++++++ .../options/point_series/point_series.tsx | 14 +++- .../public/expression_functions/xy_vis_fn.ts | 14 ++++ .../public/sample_vis.test.mocks.ts | 4 ++ src/plugins/vis_type_xy/public/to_ast.ts | 2 + src/plugins/vis_type_xy/public/types/param.ts | 4 ++ .../vis_type_xy/public/vis_component.tsx | 2 + .../vis_type_xy/public/vis_types/area.ts | 2 + .../vis_type_xy/public/vis_types/histogram.ts | 2 + .../public/vis_types/horizontal_bar.ts | 2 + .../vis_type_xy/public/vis_types/line.ts | 2 + 33 files changed, 285 insertions(+), 11 deletions(-) create mode 100644 src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx create mode 100644 src/plugins/vis_default_editor/public/components/options/long_legend_options.tsx diff --git a/src/plugins/vis_default_editor/public/components/options/index.ts b/src/plugins/vis_default_editor/public/components/options/index.ts index 31b09977f5c995..62ce76014f9fcd 100644 --- a/src/plugins/vis_default_editor/public/components/options/index.ts +++ b/src/plugins/vis_default_editor/public/components/options/index.ts @@ -16,3 +16,4 @@ export { RangeOption } from './range'; export { RequiredNumberInputOption } from './required_number_input'; export { TextInputOption } from './text_input'; export { PercentageModeOption } from './percentage_mode'; +export { LongLegendOptions } from './long_legend_options'; diff --git a/src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx b/src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx new file mode 100644 index 00000000000000..69994bb2792788 --- /dev/null +++ b/src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { mountWithIntl } from '@kbn/test/jest'; +import { LongLegendOptions, LongLegendOptionsProps } from './long_legend_options'; +import { EuiFieldNumber } from '@elastic/eui'; + +describe('LongLegendOptions', () => { + let props: LongLegendOptionsProps; + let component; + beforeAll(() => { + props = { + truncateLegend: true, + setValue: jest.fn(), + }; + }); + + it('renders the EuiFieldNumber', () => { + component = mountWithIntl(); + expect(component.find(EuiFieldNumber).length).toBe(1); + }); + + it('should call setValue when value is changes in the number input', () => { + component = mountWithIntl(); + const numberField = component.find(EuiFieldNumber); + numberField.props().onChange!(({ + target: { + value: 3, + }, + } as unknown) as React.ChangeEvent); + + expect(props.setValue).toHaveBeenCalledWith('maxLegendLines', 3); + }); + + it('input number should be disabled when truncate is false', () => { + props.truncateLegend = false; + component = mountWithIntl(); + const numberField = component.find(EuiFieldNumber); + + expect(numberField.props().disabled).toBeTruthy(); + }); +}); diff --git a/src/plugins/vis_default_editor/public/components/options/long_legend_options.tsx b/src/plugins/vis_default_editor/public/components/options/long_legend_options.tsx new file mode 100644 index 00000000000000..c06fb94376dbeb --- /dev/null +++ b/src/plugins/vis_default_editor/public/components/options/long_legend_options.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; +import { SwitchOption } from './switch'; + +const MAX_TRUNCATE_LINES = 5; +const MIN_TRUNCATE_LINES = 1; + +export interface LongLegendOptionsProps { + setValue: (paramName: 'maxLegendLines' | 'truncateLegend', value: boolean | number) => void; + truncateLegend: boolean; + maxLegendLines?: number; + 'data-test-subj'?: string; +} + +function LongLegendOptions({ + 'data-test-subj': dataTestSubj, + setValue, + truncateLegend, + maxLegendLines, +}: LongLegendOptionsProps) { + return ( + <> + + + } + > + { + const val = Number(e.target.value); + setValue( + 'maxLegendLines', + Math.min(MAX_TRUNCATE_LINES, Math.max(val, MIN_TRUNCATE_LINES)) + ); + }} + /> + + + ); +} + +export { LongLegendOptions }; diff --git a/src/plugins/vis_type_pie/public/__snapshots__/pie_fn.test.ts.snap b/src/plugins/vis_type_pie/public/__snapshots__/pie_fn.test.ts.snap index 6c43072b97c281..fb51717d1adc0f 100644 --- a/src/plugins/vis_type_pie/public/__snapshots__/pie_fn.test.ts.snap +++ b/src/plugins/vis_type_pie/public/__snapshots__/pie_fn.test.ts.snap @@ -57,6 +57,7 @@ Object { "valuesFormat": "percent", }, "legendPosition": "right", + "maxLegendLines": true, "metric": Object { "accessor": 0, "aggType": "count", @@ -72,6 +73,7 @@ Object { }, "splitColumn": undefined, "splitRow": undefined, + "truncateLegend": true, }, "visData": Object { "columns": Array [ diff --git a/src/plugins/vis_type_pie/public/editor/components/pie.test.tsx b/src/plugins/vis_type_pie/public/editor/components/pie.test.tsx index 524986524fd7e5..d37f4c10ea9ea8 100644 --- a/src/plugins/vis_type_pie/public/editor/components/pie.test.tsx +++ b/src/plugins/vis_type_pie/public/editor/components/pie.test.tsx @@ -73,6 +73,20 @@ describe('PalettePicker', function () { }); }); + it('renders the long legend options for the elastic charts implementation', async () => { + component = mountWithIntl(); + await act(async () => { + expect(findTestSubject(component, 'pieLongLegendsOptions').length).toBe(1); + }); + }); + + it('not renders the long legend options for the vislib implementation', async () => { + component = mountWithIntl(); + await act(async () => { + expect(findTestSubject(component, 'pieLongLegendsOptions').length).toBe(0); + }); + }); + it('renders the label position dropdown for the elastic charts implementation', async () => { component = mountWithIntl(); await act(async () => { diff --git a/src/plugins/vis_type_pie/public/editor/components/pie.tsx b/src/plugins/vis_type_pie/public/editor/components/pie.tsx index 8ce4f4defbaed8..3bf28ba58d4eb1 100644 --- a/src/plugins/vis_type_pie/public/editor/components/pie.tsx +++ b/src/plugins/vis_type_pie/public/editor/components/pie.tsx @@ -26,6 +26,7 @@ import { SwitchOption, SelectOption, PalettePicker, + LongLegendOptions, } from '../../../../vis_default_editor/public'; import { VisEditorOptionsProps } from '../../../../visualizations/public'; import { TruncateLabelsOption } from './truncate_labels'; @@ -169,6 +170,12 @@ const PieOptions = (props: PieOptionsProps) => { }} data-test-subj="visTypePieNestedLegendSwitch" /> + )} {props.showElasticChartsOptions && palettesRegistry && ( @@ -276,7 +283,13 @@ const PieOptions = (props: PieOptionsProps) => { /> )} - + ); diff --git a/src/plugins/vis_type_pie/public/editor/components/truncate_labels.tsx b/src/plugins/vis_type_pie/public/editor/components/truncate_labels.tsx index e6eb56725753c6..d4c798498e8b05 100644 --- a/src/plugins/vis_type_pie/public/editor/components/truncate_labels.tsx +++ b/src/plugins/vis_type_pie/public/editor/components/truncate_labels.tsx @@ -8,7 +8,7 @@ import React, { ChangeEvent } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; +import { EuiFormRow, EuiFieldNumber, EuiIconTip } from '@elastic/eui'; export interface TruncateLabelsOptionProps { disabled?: boolean; @@ -27,6 +27,16 @@ function TruncateLabelsOption({ disabled, value = null, setValue }: TruncateLabe })} fullWidth display="rowCompressed" + labelAppend={ + + } > { }, legendPosition: 'right', nestedLegend: false, + maxLegendLines: 1, + truncateLegend: true, distinctColors: false, palette: { name: 'default', diff --git a/src/plugins/vis_type_pie/public/pie_component.tsx b/src/plugins/vis_type_pie/public/pie_component.tsx index c0f4a8a6112f8d..9119f2f2ecd6c3 100644 --- a/src/plugins/vis_type_pie/public/pie_component.tsx +++ b/src/plugins/vis_type_pie/public/pie_component.tsx @@ -320,7 +320,16 @@ const PieComponent = (props: PieComponentProps) => { services.actions, services.fieldFormats )} - theme={chartTheme} + theme={[ + chartTheme, + { + legend: { + labelOptions: { + maxLines: visParams.truncateLegend ? visParams.maxLegendLines ?? 1 : 0, + }, + }, + }, + ]} baseTheme={chartBaseTheme} onRenderChange={onRenderChange} /> diff --git a/src/plugins/vis_type_pie/public/pie_fn.test.ts b/src/plugins/vis_type_pie/public/pie_fn.test.ts index 3dcef406379c26..33b5f38cbe630f 100644 --- a/src/plugins/vis_type_pie/public/pie_fn.test.ts +++ b/src/plugins/vis_type_pie/public/pie_fn.test.ts @@ -23,6 +23,8 @@ describe('interpreter/functions#pie', () => { legendPosition: 'right', isDonut: true, nestedLegend: true, + truncateLegend: true, + maxLegendLines: true, distinctColors: false, palette: 'kibana_palette', labels: { diff --git a/src/plugins/vis_type_pie/public/pie_fn.ts b/src/plugins/vis_type_pie/public/pie_fn.ts index 65ac648ca28684..c5987001d4494d 100644 --- a/src/plugins/vis_type_pie/public/pie_fn.ts +++ b/src/plugins/vis_type_pie/public/pie_fn.ts @@ -89,6 +89,19 @@ export const createPieVisFn = (): VisTypePieExpressionFunctionDefinition => ({ }), default: false, }, + truncateLegend: { + types: ['boolean'], + help: i18n.translate('visTypePie.function.args.truncateLegendHelpText', { + defaultMessage: 'Defines if the legend items will be truncated or not', + }), + default: true, + }, + maxLegendLines: { + types: ['number'], + help: i18n.translate('visTypePie.function.args.maxLegendLinesHelpText', { + defaultMessage: 'Defines the number of lines per legend item', + }), + }, distinctColors: { types: ['boolean'], help: i18n.translate('visTypePie.function.args.distinctColorsHelpText', { diff --git a/src/plugins/vis_type_pie/public/sample_vis.test.mocks.ts b/src/plugins/vis_type_pie/public/sample_vis.test.mocks.ts index 41fa00bbe2386d..26d9c526a81371 100644 --- a/src/plugins/vis_type_pie/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_type_pie/public/sample_vis.test.mocks.ts @@ -28,6 +28,8 @@ export const samplePieVis = { legendPosition: 'right', isDonut: true, nestedLegend: true, + truncateLegend: true, + maxLegendLines: 1, distinctColors: false, palette: 'kibana_palette', labels: { diff --git a/src/plugins/vis_type_pie/public/to_ast.ts b/src/plugins/vis_type_pie/public/to_ast.ts index e8c9f301b4366d..b360e375bf40dd 100644 --- a/src/plugins/vis_type_pie/public/to_ast.ts +++ b/src/plugins/vis_type_pie/public/to_ast.ts @@ -50,6 +50,8 @@ export const toExpressionAst: VisToExpressionAst = async (vis, par addLegend: vis.params.addLegend, legendPosition: vis.params.legendPosition, nestedLegend: vis.params?.nestedLegend, + truncateLegend: vis.params.truncateLegend, + maxLegendLines: vis.params.maxLegendLines, distinctColors: vis.params?.distinctColors, isDonut: vis.params.isDonut, palette: vis.params?.palette?.name, diff --git a/src/plugins/vis_type_pie/public/types/types.ts b/src/plugins/vis_type_pie/public/types/types.ts index 4f3365545d0628..94eaeb55f72428 100644 --- a/src/plugins/vis_type_pie/public/types/types.ts +++ b/src/plugins/vis_type_pie/public/types/types.ts @@ -33,6 +33,8 @@ interface PieCommonParams { addLegend: boolean; legendPosition: Position; nestedLegend: boolean; + truncateLegend: boolean; + maxLegendLines: number; distinctColors: boolean; isDonut: boolean; } diff --git a/src/plugins/vis_type_pie/public/utils/get_columns.test.ts b/src/plugins/vis_type_pie/public/utils/get_columns.test.ts index 3170628ec2e125..9f64266ed0e0e3 100644 --- a/src/plugins/vis_type_pie/public/utils/get_columns.test.ts +++ b/src/plugins/vis_type_pie/public/utils/get_columns.test.ts @@ -144,6 +144,8 @@ describe('getColumns', () => { }, legendPosition: 'right', nestedLegend: false, + maxLegendLines: 1, + truncateLegend: false, palette: { name: 'default', type: 'palette', diff --git a/src/plugins/vis_type_pie/public/utils/get_config.ts b/src/plugins/vis_type_pie/public/utils/get_config.ts index a8a4edb01cd9c8..40f8f84b127f97 100644 --- a/src/plugins/vis_type_pie/public/utils/get_config.ts +++ b/src/plugins/vis_type_pie/public/utils/get_config.ts @@ -63,6 +63,7 @@ export const getConfig = ( config.linkLabel = { maxCount: Number.POSITIVE_INFINITY, maximumSection: Number.POSITIVE_INFINITY, + maxTextLength: visParams.labels.truncate ?? undefined, }; } diff --git a/src/plugins/vis_type_pie/public/utils/get_layers.ts b/src/plugins/vis_type_pie/public/utils/get_layers.ts index b995df83c0bb04..42c4650419c6b1 100644 --- a/src/plugins/vis_type_pie/public/utils/get_layers.ts +++ b/src/plugins/vis_type_pie/public/utils/get_layers.ts @@ -151,12 +151,7 @@ export const getLayers = ( showAccessor: (d: Datum) => d !== EMPTY_SLICE, nodeLabel: (d: unknown) => { if (col.format) { - const formattedLabel = formatter.deserialize(col.format).convert(d) ?? ''; - if (visParams.labels.truncate && formattedLabel.length <= visParams.labels.truncate) { - return formattedLabel; - } else { - return `${formattedLabel.slice(0, Number(visParams.labels.truncate))}\u2026`; - } + return formatter.deserialize(col.format).convert(d) ?? ''; } return String(d); }, diff --git a/src/plugins/vis_type_pie/public/vis_type/pie.ts b/src/plugins/vis_type_pie/public/vis_type/pie.ts index 9d1556ac33ad7e..95a9d0d41481b9 100644 --- a/src/plugins/vis_type_pie/public/vis_type/pie.ts +++ b/src/plugins/vis_type_pie/public/vis_type/pie.ts @@ -35,6 +35,8 @@ export const getPieVisTypeDefinition = ({ addLegend: !showElasticChartsOptions, legendPosition: Position.Right, nestedLegend: false, + truncateLegend: true, + maxLegendLines: 1, distinctColors: false, isDonut: true, palette: { diff --git a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap index 8b720568c4d2c4..233940d97d38a5 100644 --- a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap @@ -8,7 +8,7 @@ Object { "area", ], "visConfig": Array [ - "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"circlesRadius\\":5,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"palette\\":{\\"name\\":\\"default\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"circlesRadius\\":5,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"truncateLegend\\":true,\\"maxLegendLines\\":1,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"palette\\":{\\"name\\":\\"default\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", ], }, "getArgument": [Function], diff --git a/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap index 7c21e699216bc3..7ee1b0d2b20535 100644 --- a/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap @@ -32,6 +32,9 @@ Object { "legendPosition": Array [ "top", ], + "maxLegendLines": Array [ + 1, + ], "palette": Array [ "default", ], @@ -51,6 +54,9 @@ Object { }, ], "times": Array [], + "truncateLegend": Array [ + true, + ], "type": Array [ "area", ], diff --git a/src/plugins/vis_type_xy/public/components/xy_settings.tsx b/src/plugins/vis_type_xy/public/components/xy_settings.tsx index 03455bae695061..2dd7d7e0a91f9f 100644 --- a/src/plugins/vis_type_xy/public/components/xy_settings.tsx +++ b/src/plugins/vis_type_xy/public/components/xy_settings.tsx @@ -60,6 +60,8 @@ type XYSettingsProps = Pick< legendAction?: LegendAction; legendColorPicker: LegendColorPicker; legendPosition: Position; + truncateLegend: boolean; + maxLegendLines: number; }; function getValueLabelsStyling() { @@ -93,6 +95,8 @@ export const XYSettings: FC = ({ legendAction, legendColorPicker, legendPosition, + maxLegendLines, + truncateLegend, }) => { const themeService = getThemeService(); const theme = themeService.useChartsTheme(); @@ -113,6 +117,9 @@ export const XYSettings: FC = ({ crosshair: { ...theme.crosshair, }, + legend: { + labelOptions: { maxLines: truncateLegend ? maxLegendLines ?? 1 : 0 }, + }, axes: { axisTitle: { padding: { diff --git a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts index d5e1360ced74c7..e51b47bc4c7fa7 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts @@ -426,6 +426,8 @@ export const getVis = (bucketType: string) => { fittingFunction: 'linear', times: [], addTimeMarker: false, + maxLegendLines: 1, + truncateLegend: true, radiusRatio: 9, thresholdLine: { show: false, @@ -849,6 +851,8 @@ export const getStateParams = (type: string, thresholdPanelOn: boolean) => { legendPosition: 'right', times: [], addTimeMarker: false, + maxLegendLines: 1, + truncateLegend: true, detailedTooltip: true, palette: { type: 'palette', diff --git a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.test.tsx index 59c03e02ac9f45..7fedd38e4e7ecc 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.test.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.test.tsx @@ -105,6 +105,26 @@ describe('PointSeries Editor', function () { }); }); + it('not renders the long legend options if showElasticChartsOptions is false', async () => { + component = mountWithIntl(); + await act(async () => { + expect(findTestSubject(component, 'xyLongLegendsOptions').length).toBe(0); + }); + }); + + it('renders the long legend options if showElasticChartsOptions is true', async () => { + const newVisProps = ({ + ...props, + extraProps: { + showElasticChartsOptions: true, + }, + } as unknown) as PointSeriesOptionsProps; + component = mountWithIntl(); + await act(async () => { + expect(findTestSubject(component, 'xyLongLegendsOptions').length).toBe(1); + }); + }); + it('not renders the fitting function for a bar chart', async () => { const newVisProps = ({ ...props, diff --git a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.tsx b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.tsx index 343976651d21e1..1fd9b043e87f5b 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.tsx @@ -11,7 +11,11 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { BasicOptions, SwitchOption } from '../../../../../../vis_default_editor/public'; +import { + BasicOptions, + SwitchOption, + LongLegendOptions, +} from '../../../../../../vis_default_editor/public'; import { BUCKET_TYPES } from '../../../../../../data/public'; import { VisParams } from '../../../../types'; @@ -58,6 +62,14 @@ export function PointSeriesOptions( + {props.extraProps?.showElasticChartsOptions && ( + + )} {vis.data.aggs!.aggs.some( (agg) => agg.schema === 'segment' && agg.type.name === BUCKET_TYPES.DATE_HISTOGRAM diff --git a/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts b/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts index 35f3b2d7c627d8..6d2b860066b075 100644 --- a/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts +++ b/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts @@ -55,6 +55,18 @@ export const visTypeXyVisFn = (): VisTypeXyExpressionFunctionDefinition => ({ defaultMessage: 'Show time marker', }), }, + truncateLegend: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.truncateLegend.help', { + defaultMessage: 'Defines if the legend will be truncated or not', + }), + }, + maxLegendLines: { + types: ['number'], + help: i18n.translate('visTypeXy.function.args.args.maxLegendLines.help', { + defaultMessage: 'Defines the maximum lines per legend item', + }), + }, addLegend: { types: ['boolean'], help: i18n.translate('visTypeXy.function.args.addLegend.help', { @@ -225,6 +237,8 @@ export const visTypeXyVisFn = (): VisTypeXyExpressionFunctionDefinition => ({ addTooltip: args.addTooltip, legendPosition: args.legendPosition, addTimeMarker: args.addTimeMarker, + maxLegendLines: args.maxLegendLines, + truncateLegend: args.truncateLegend, categoryAxes: args.categoryAxes.map((categoryAxis) => ({ ...categoryAxis, type: categoryAxis.axisType, diff --git a/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts b/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts index 8fafd4c7230557..7fff29edfab51b 100644 --- a/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts @@ -88,6 +88,8 @@ export const sampleAreaVis = { legendPosition: 'right', times: [], addTimeMarker: false, + truncateLegend: true, + maxLegendLines: 1, thresholdLine: { show: false, value: 10, @@ -255,6 +257,8 @@ export const sampleAreaVis = { legendPosition: 'top', times: [], addTimeMarker: false, + truncateLegend: true, + maxLegendLines: 1, thresholdLine: { show: false, value: 10, diff --git a/src/plugins/vis_type_xy/public/to_ast.ts b/src/plugins/vis_type_xy/public/to_ast.ts index 9fec3f99ab39b5..0b1eb5262d71ac 100644 --- a/src/plugins/vis_type_xy/public/to_ast.ts +++ b/src/plugins/vis_type_xy/public/to_ast.ts @@ -194,6 +194,8 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params type: vis.type.name as XyVisType, chartType: vis.params.type, addTimeMarker: vis.params.addTimeMarker, + truncateLegend: vis.params.truncateLegend, + maxLegendLines: vis.params.maxLegendLines, addLegend: vis.params.addLegend, addTooltip: vis.params.addTooltip, legendPosition: vis.params.legendPosition, diff --git a/src/plugins/vis_type_xy/public/types/param.ts b/src/plugins/vis_type_xy/public/types/param.ts index 421690f7fc6a9f..0687bd2af2cd13 100644 --- a/src/plugins/vis_type_xy/public/types/param.ts +++ b/src/plugins/vis_type_xy/public/types/param.ts @@ -121,6 +121,8 @@ export interface VisParams { addTooltip: boolean; legendPosition: Position; addTimeMarker: boolean; + truncateLegend: boolean; + maxLegendLines: number; categoryAxes: CategoryAxis[]; orderBucketsBySum?: boolean; labels: Labels; @@ -158,6 +160,8 @@ export interface XYVisConfig { addTooltip: boolean; legendPosition: Position; addTimeMarker: boolean; + truncateLegend: boolean; + maxLegendLines: number; orderBucketsBySum?: boolean; labels: ExpressionValueLabel; thresholdLine: ExpressionValueThresholdLine; diff --git a/src/plugins/vis_type_xy/public/vis_component.tsx b/src/plugins/vis_type_xy/public/vis_component.tsx index 2dffabb2ba0b9c..346f6cc74a1ac4 100644 --- a/src/plugins/vis_type_xy/public/vis_component.tsx +++ b/src/plugins/vis_type_xy/public/vis_component.tsx @@ -345,6 +345,8 @@ const VisComponent = (props: VisComponentProps) => { /> Date: Thu, 19 Aug 2021 12:27:42 +0100 Subject: [PATCH 36/36] chore(NA): moving @kbn/plugin-generator to babel transpiler (#109083) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-plugin-generator/.babelrc | 3 +++ packages/kbn-plugin-generator/BUILD.bazel | 23 +++++++++++++++------ packages/kbn-plugin-generator/package.json | 4 ++-- packages/kbn-plugin-generator/tsconfig.json | 5 +++-- 4 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 packages/kbn-plugin-generator/.babelrc diff --git a/packages/kbn-plugin-generator/.babelrc b/packages/kbn-plugin-generator/.babelrc new file mode 100644 index 00000000000000..7da72d17791281 --- /dev/null +++ b/packages/kbn-plugin-generator/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"] +} diff --git a/packages/kbn-plugin-generator/BUILD.bazel b/packages/kbn-plugin-generator/BUILD.bazel index c16862ee4f3c21..c935d1763dae8c 100644 --- a/packages/kbn-plugin-generator/BUILD.bazel +++ b/packages/kbn-plugin-generator/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-plugin-generator" PKG_REQUIRE_NAME = "@kbn/plugin-generator" @@ -35,7 +36,7 @@ NPM_MODULE_EXTRA_FILES = [ ":template", ] -SRC_DEPS = [ +RUNTIME_DEPS = [ "//packages/kbn-utils", "//packages/kbn-dev-utils", "@npm//del", @@ -49,6 +50,11 @@ SRC_DEPS = [ ] TYPES_DEPS = [ + "//packages/kbn-utils", + "//packages/kbn-dev-utils", + "@npm//del", + "@npm//execa", + "@npm//globby", "@npm//@types/ejs", "@npm//@types/inquirer", "@npm//@types/jest", @@ -58,7 +64,11 @@ TYPES_DEPS = [ "@npm//@types/vinyl-fs", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) ts_config( name = "tsconfig", @@ -70,13 +80,14 @@ ts_config( ) ts_project( - name = "tsc", + name = "tsc_types", args = ['--pretty'], srcs = SRCS, - deps = DEPS, + deps = TYPES_DEPS, declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", source_map = True, root_dir = "src", tsconfig = ":tsconfig", @@ -85,7 +96,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = DEPS + [":tsc"], + deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) diff --git a/packages/kbn-plugin-generator/package.json b/packages/kbn-plugin-generator/package.json index 298373afd2f249..d30f25e478dcfb 100644 --- a/packages/kbn-plugin-generator/package.json +++ b/packages/kbn-plugin-generator/package.json @@ -3,6 +3,6 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "target/index.js", - "types": "target/index.d.ts" + "main": "target_node/index.js", + "types": "target_types/index.d.ts" } \ No newline at end of file diff --git a/packages/kbn-plugin-generator/tsconfig.json b/packages/kbn-plugin-generator/tsconfig.json index 6a25803c83940e..5b666cf801da63 100644 --- a/packages/kbn-plugin-generator/tsconfig.json +++ b/packages/kbn-plugin-generator/tsconfig.json @@ -1,12 +1,13 @@ { "extends": "../../tsconfig.bazel.json", "compilerOptions": { - "outDir": "target", - "target": "ES2019", "declaration": true, "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-plugin-generator/src", + "target": "ES2019", "types": [ "jest", "node"