diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index d4d2b229eeba77..c79e46c1d91731 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -168,6 +168,10 @@ Content is fetched from the remote (https://feeds.elastic.co and https://feeds-s |Create choropleth maps. Display the results of a term-aggregation as e.g. countries, zip-codes, states. +|{kib-repo}blob/{branch}/src/plugins/runtime_fields/README.mdx[runtimeFields] +|The runtime fields plugin provides types and constants for OSS and xpack runtime field related code. + + |{kib-repo}blob/{branch}/src/plugins/saved_objects/README.md[savedObjects] |The savedObjects plugin exposes utilities to manipulate saved objects on the client side. @@ -483,8 +487,8 @@ Elastic. |Welcome to the Kibana rollup plugin! This plugin provides Kibana support for Elasticsearch's rollup feature. Please refer to the Elasticsearch documentation to understand rollup indices and how to create rollup jobs. -|{kib-repo}blob/{branch}/x-pack/plugins/runtime_fields/README.md[runtimeFields] -|Welcome to the home of the runtime field editor and everything related to runtime fields! +|{kib-repo}blob/{branch}/x-pack/plugins/runtime_field_editor/README.md[runtimeFieldEditor] +|Welcome to the home of the runtime field editor! |{kib-repo}blob/{branch}/x-pack/plugins/saved_objects_tagging/README.md[savedObjectsTagging] diff --git a/package.json b/package.json index e7db32079b3f49..03e9bd033cf19c 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,16 @@ "@loaders.gl/json": "^2.3.1", "@slack/webhook": "^5.0.0", "@storybook/addons": "^6.0.16", + "@turf/along": "6.0.1", + "@turf/area": "6.0.1", + "@turf/bbox": "6.0.1", + "@turf/bbox-polygon": "6.0.1", + "@turf/boolean-contains": "6.0.1", + "@turf/center-of-mass": "6.0.1", "@turf/circle": "6.0.1", + "@turf/distance": "6.0.1", + "@turf/helpers": "6.0.1", + "@turf/length": "^6.0.2", "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "abortcontroller-polyfill": "^1.4.0", @@ -399,11 +408,6 @@ "@testing-library/react": "^11.0.4", "@testing-library/react-hooks": "^3.4.1", "@testing-library/user-event": "^12.1.6", - "@turf/bbox": "6.0.1", - "@turf/bbox-polygon": "6.0.1", - "@turf/boolean-contains": "6.0.1", - "@turf/distance": "6.0.1", - "@turf/helpers": "6.0.1", "@types/accept": "3.1.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 08d883a7cbb4d5..67287089489e17 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -102,6 +102,7 @@ pageLoadAssetSize: visualizations: 295025 visualize: 57431 watcher: 43598 - runtimeFields: 41752 + runtimeFields: 10000 stackAlerts: 29684 presentationUtil: 28545 + runtimeFieldEditor: 46986 diff --git a/src/dev/run_find_plugins_without_ts_refs.ts b/src/dev/run_find_plugins_without_ts_refs.ts index ad63884671e245..995a22bf3e583c 100644 --- a/src/dev/run_find_plugins_without_ts_refs.ts +++ b/src/dev/run_find_plugins_without_ts_refs.ts @@ -19,6 +19,7 @@ import Path from 'path'; import Fs from 'fs'; +import JSON5 from 'json5'; import { get } from 'lodash'; import { run } from '@kbn/dev-utils'; import { getPluginDeps, findPlugins } from './plugin_discovery'; @@ -88,7 +89,7 @@ function isMigratedToTsProjectRefs(dir: string): boolean { try { const path = Path.join(dir, 'tsconfig.json'); const content = Fs.readFileSync(path, { encoding: 'utf8' }); - return get(JSON.parse(content), 'compilerOptions.composite', false); + return get(JSON5.parse(content), 'compilerOptions.composite', false); } catch (e) { return false; } diff --git a/src/plugins/apm_oss/tsconfig.json b/src/plugins/apm_oss/tsconfig.json new file mode 100644 index 00000000000000..aeb6837c69a998 --- /dev/null +++ b/src/plugins/apm_oss/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "server/tutorial/index_pattern.json" + ], + "references": [{ "path": "../../core/tsconfig.json" }, { "path": "../home/tsconfig.json" }] +} diff --git a/src/plugins/runtime_fields/README.mdx b/src/plugins/runtime_fields/README.mdx new file mode 100644 index 00000000000000..15985b07caf96b --- /dev/null +++ b/src/plugins/runtime_fields/README.mdx @@ -0,0 +1,4 @@ + +# Runtime Fields + +The runtime fields plugin provides types and constants for OSS and xpack runtime field related code. diff --git a/src/plugins/runtime_fields/common/constants.ts b/src/plugins/runtime_fields/common/constants.ts new file mode 100644 index 00000000000000..568003508f4bda --- /dev/null +++ b/src/plugins/runtime_fields/common/constants.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const; diff --git a/src/plugins/runtime_fields/common/index.ts b/src/plugins/runtime_fields/common/index.ts new file mode 100644 index 00000000000000..b08ac661a4bd6e --- /dev/null +++ b/src/plugins/runtime_fields/common/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './constants'; +export * from './types'; diff --git a/src/plugins/runtime_fields/common/types.ts b/src/plugins/runtime_fields/common/types.ts new file mode 100644 index 00000000000000..f16d3d75d6ecf5 --- /dev/null +++ b/src/plugins/runtime_fields/common/types.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RUNTIME_FIELD_TYPES } from './constants'; + +export type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; +export interface RuntimeField { + name: string; + type: RuntimeType; + script: { + source: string; + }; +} diff --git a/src/plugins/runtime_fields/kibana.json b/src/plugins/runtime_fields/kibana.json new file mode 100644 index 00000000000000..e71116f81532e4 --- /dev/null +++ b/src/plugins/runtime_fields/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "runtimeFields", + "version": "kibana", + "server": false, + "ui": true +} diff --git a/src/plugins/runtime_fields/public/index.ts b/src/plugins/runtime_fields/public/index.ts new file mode 100644 index 00000000000000..a7a94b07ac6e8b --- /dev/null +++ b/src/plugins/runtime_fields/public/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from '../common'; + +export function plugin() { + return { + setup() {}, + start() {}, + stop() {}, + }; +} diff --git a/tsconfig.json b/tsconfig.json index 75e1b097c734f4..d882697bbf4841 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "exclude": [ "src/**/__fixtures__/**/*", "src/core/**/*", + "src/plugins/apm_oss/**/*", "src/plugins/bfetch/**/*", "src/plugins/data/**/*", "src/plugins/dev_tools/**/*", @@ -28,7 +29,7 @@ "src/plugins/telemetry_collection_manager/**/*", "src/plugins/ui_actions/**/*", "src/plugins/url_forwarding/**/*", - "src/plugins/usage_collection/**/*", + "src/plugins/usage_collection/**/*" // In the build we actually exclude **/public/**/* from this config so that // we can run the TSC on both this and the .browser version of this config // file, but if we did it during development IDEs would not be able to find @@ -37,6 +38,7 @@ ], "references": [ { "path": "./src/core/tsconfig.json" }, + { "path": "./src/plugins/apm_oss/tsconfig.json" }, { "path": "./src/plugins/bfetch/tsconfig.json" }, { "path": "./src/plugins/data/tsconfig.json" }, { "path": "./src/plugins/dev_tools/tsconfig.json" }, @@ -58,6 +60,6 @@ { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/ui_actions/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, - { "path": "./src/plugins/usage_collection/tsconfig.json" }, + { "path": "./src/plugins/usage_collection/tsconfig.json" } ] } diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 282bf7fb1f0116..c712d46204f355 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -2,6 +2,7 @@ "include": [], "references": [ { "path": "./src/core/tsconfig.json" }, + { "path": "./src/plugins/apm_oss/tsconfig.json" }, { "path": "./src/plugins/bfetch/tsconfig.json" }, { "path": "./src/plugins/data/tsconfig.json" }, { "path": "./src/plugins/dev_tools/tsconfig.json" }, @@ -23,6 +24,6 @@ { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/ui_actions/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, - { "path": "./src/plugins/usage_collection/tsconfig.json" }, + { "path": "./src/plugins/usage_collection/tsconfig.json" } ] } diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 6937862d20536d..7380d25930bc02 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -42,7 +42,7 @@ "xpack.remoteClusters": "plugins/remote_clusters", "xpack.reporting": ["plugins/reporting"], "xpack.rollupJobs": ["plugins/rollup"], - "xpack.runtimeFields": "plugins/runtime_fields", + "xpack.runtimeFields": "plugins/runtime_field_editor", "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": "plugins/security", "xpack.server": "legacy/server", diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/List.test.tsx similarity index 78% rename from x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx rename to x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/List.test.tsx index 4022caedadaabc..e6555ed900a6d7 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/List.test.tsx @@ -6,11 +6,11 @@ import { mount } from 'enzyme'; import React from 'react'; -import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; -import { MockUrlParamsContextProvider } from '../../../../../context/url_params_context/mock_url_params_context_provider'; -import { mockMoment, toJson } from '../../../../../utils/testHelpers'; -import { ErrorGroupList } from '../index'; -import props from './props.json'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; +import { mockMoment, toJson } from '../../../../utils/testHelpers'; +import { ErrorGroupList } from './index'; +import props from './__fixtures__/props.json'; import { MemoryRouter } from 'react-router-dom'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/props.json b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__fixtures__/props.json similarity index 100% rename from x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/props.json rename to x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__fixtures__/props.json diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__snapshots__/List.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap rename to x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__snapshots__/List.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx similarity index 86% rename from x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx rename to x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx index a492938deffab6..c469a2c21c34a8 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx @@ -5,9 +5,9 @@ */ import React from 'react'; import { createMemoryHistory } from 'history'; -import * as fetcherHook from '../../../../../../hooks/use_fetcher'; -import { SelectableUrlList } from '../SelectableUrlList'; -import { render } from '../../../utils/test_helper'; +import * as fetcherHook from '../../../../../hooks/use_fetcher'; +import { SelectableUrlList } from './SelectableUrlList'; +import { render } from '../../utils/test_helper'; describe('SelectableUrlList', () => { it('it uses search term value from url', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/FormatToSec.test.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/FormatToSec.test.ts similarity index 94% rename from x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/FormatToSec.test.ts rename to x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/FormatToSec.test.ts index 6cdf469d980faa..764d662615031e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/FormatToSec.test.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/FormatToSec.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { formatToSec } from '../KeyUXMetrics'; +import { formatToSec } from './KeyUXMetrics'; describe('FormatToSec', () => { test('it returns the expected value', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx rename to x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx index 5d73cbc4cd3c80..804eeaec266551 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; import { render } from '@testing-library/react'; -import * as fetcherHook from '../../../../../hooks/use_fetcher'; -import { KeyUXMetrics } from '../KeyUXMetrics'; +import * as fetcherHook from '../../../../hooks/use_fetcher'; +import { KeyUXMetrics } from './KeyUXMetrics'; describe('KeyUXMetrics', () => { it('renders metrics with correct formats', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/EmbeddedMap.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.test.tsx similarity index 79% rename from x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/EmbeddedMap.test.tsx rename to x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.test.tsx index 388a8824bc73d1..125c57f514a594 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/EmbeddedMap.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.test.tsx @@ -7,9 +7,9 @@ import { render } from 'enzyme'; import React from 'react'; -import { EmbeddedMap } from '../EmbeddedMap'; -import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { embeddablePluginMock } from '../../../../../../../../../src/plugins/embeddable/public/mocks'; +import { EmbeddedMap } from './EmbeddedMap'; +import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; +import { embeddablePluginMock } from '../../../../../../../../src/plugins/embeddable/public/mocks'; describe('Embedded Map', () => { test('it renders', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/MapToolTip.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/MapToolTip.test.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/MapToolTip.test.tsx rename to x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/MapToolTip.test.tsx index cbaae40b043611..89f20bf24ccba8 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/MapToolTip.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/MapToolTip.test.tsx @@ -7,7 +7,7 @@ import { render, shallow } from 'enzyme'; import React from 'react'; -import { MapToolTip } from '../MapToolTip'; +import { MapToolTip } from './MapToolTip'; describe('Map Tooltip', () => { test('it shallow renders', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__mocks__/regions_layer.mock.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts rename to x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__mocks__/regions_layer.mock.ts diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__snapshots__/EmbeddedMap.test.tsx.snap b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__snapshots__/EmbeddedMap.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__snapshots__/EmbeddedMap.test.tsx.snap rename to x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__snapshots__/EmbeddedMap.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__snapshots__/MapToolTip.test.tsx.snap b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__snapshots__/MapToolTip.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__snapshots__/MapToolTip.test.tsx.snap rename to x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__snapshots__/MapToolTip.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/useLayerList.test.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.test.ts similarity index 92% rename from x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/useLayerList.test.ts rename to x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.test.ts index 872553452b2637..a63ab11263e5fc 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/useLayerList.test.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.test.ts @@ -6,7 +6,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { mockLayerList } from './__mocks__/regions_layer.mock'; -import { useLayerList } from '../useLayerList'; +import { useLayerList } from './useLayerList'; describe('useLayerList', () => { test('it returns the region layer', () => { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/__test__/distribution.test.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/distribution.test.ts similarity index 96% rename from x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/__test__/distribution.test.ts rename to x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/distribution.test.ts index 1586e1f4903a2d..0453b113f41de4 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/__test__/distribution.test.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/distribution.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getFormattedBuckets } from '../index'; +import { getFormattedBuckets } from './index'; describe('Distribution', () => { it('getFormattedBuckets', () => { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_agent_marks.test.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.test.ts similarity index 90% rename from x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_agent_marks.test.ts rename to x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.test.ts index 72533cf2930d26..7666db35d43cf2 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_agent_marks.test.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Transaction } from '../../../../../../../../typings/es_schemas/ui/transaction'; -import { getAgentMarks } from '../get_agent_marks'; +import { Transaction } from '../../../../../../../typings/es_schemas/ui/transaction'; +import { getAgentMarks } from './get_agent_marks'; describe('getAgentMarks', () => { it('should sort the marks by time', () => { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.test.ts similarity index 94% rename from x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts rename to x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.test.ts index abfecc3f70d241..0eb7a5b89aa3a2 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IWaterfallError } from '../../Waterfall/waterfall_helpers/waterfall_helpers'; -import { getErrorMarks } from '../get_error_marks'; +import { IWaterfallError } from '../Waterfall/waterfall_helpers/waterfall_helpers'; +import { getErrorMarks } from './get_error_marks'; describe('getErrorMarks', () => { describe('returns empty array', () => { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index 6bb1ea2919c166..e501dd3bb7a56f 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -21,7 +21,7 @@ import { import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import * as useLocalUIFilters from '../../../hooks/useLocalUIFilters'; import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; -import { SessionStorageMock } from '../../../services/__test__/SessionStorageMock'; +import { SessionStorageMock } from '../../../services/__mocks__/SessionStorageMock'; import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; import * as hook from './use_anomaly_detection_jobs_fetcher'; diff --git a/x-pack/plugins/apm/public/components/shared/ImpactBar/__test__/ImpactBar.test.js b/x-pack/plugins/apm/public/components/shared/ImpactBar/ImpactBar.test.js similarity index 95% rename from x-pack/plugins/apm/public/components/shared/ImpactBar/__test__/ImpactBar.test.js rename to x-pack/plugins/apm/public/components/shared/ImpactBar/ImpactBar.test.js index d4b3f223f726fc..4e94ea85c120bd 100644 --- a/x-pack/plugins/apm/public/components/shared/ImpactBar/__test__/ImpactBar.test.js +++ b/x-pack/plugins/apm/public/components/shared/ImpactBar/ImpactBar.test.js @@ -6,7 +6,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { ImpactBar } from '..'; +import { ImpactBar } from '.'; describe('ImpactBar component', () => { it('should render with default values', () => { diff --git a/x-pack/plugins/apm/public/components/shared/ImpactBar/__test__/__snapshots__/ImpactBar.test.js.snap b/x-pack/plugins/apm/public/components/shared/ImpactBar/__snapshots__/ImpactBar.test.js.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/ImpactBar/__test__/__snapshots__/ImpactBar.test.js.snap rename to x-pack/plugins/apm/public/components/shared/ImpactBar/__snapshots__/ImpactBar.test.js.snap diff --git a/x-pack/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx b/x-pack/plugins/apm/public/components/shared/KeyValueTable/KeyValueTable.test.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx rename to x-pack/plugins/apm/public/components/shared/KeyValueTable/KeyValueTable.test.tsx index 5a9e8809ea7349..a08ade8e559d0b 100644 --- a/x-pack/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/KeyValueTable/KeyValueTable.test.tsx @@ -5,9 +5,9 @@ */ import React from 'react'; -import { KeyValueTable } from '..'; +import { KeyValueTable } from '.'; import { render } from '@testing-library/react'; -import { renderWithTheme } from '../../../../utils/testHelpers'; +import { renderWithTheme } from '../../../utils/testHelpers'; function getKeys(output: ReturnType) { const keys = output.getAllByTestId('dot-key'); diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverErrorButton.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverErrorButton.test.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverErrorButton.test.tsx rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverErrorButton.test.tsx index f71c8b71aa2ee5..3a41c19c53f6d1 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverErrorButton.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverErrorButton.test.tsx @@ -6,8 +6,8 @@ import { shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; -import { APMError } from '../../../../../../typings/es_schemas/ui/apm_error'; -import { DiscoverErrorLink } from '../DiscoverErrorLink'; +import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; +import { DiscoverErrorLink } from './DiscoverErrorLink'; describe('DiscoverErrorLink without kuery', () => { let wrapper: ShallowWrapper; diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverErrorLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverErrorLink.test.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverErrorLink.test.tsx rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverErrorLink.test.tsx index f71c8b71aa2ee5..3a41c19c53f6d1 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverErrorLink.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverErrorLink.test.tsx @@ -6,8 +6,8 @@ import { shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; -import { APMError } from '../../../../../../typings/es_schemas/ui/apm_error'; -import { DiscoverErrorLink } from '../DiscoverErrorLink'; +import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; +import { DiscoverErrorLink } from './DiscoverErrorLink'; describe('DiscoverErrorLink without kuery', () => { let wrapper: ShallowWrapper; diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLinks.integration.test.tsx similarity index 87% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLinks.integration.test.tsx index ca02abc3959929..e77d4d71852731 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLinks.integration.test.tsx @@ -6,13 +6,13 @@ import { Location } from 'history'; import React from 'react'; -import { APMError } from '../../../../../../typings/es_schemas/ui/apm_error'; -import { Span } from '../../../../../../typings/es_schemas/ui/span'; -import { Transaction } from '../../../../../../typings/es_schemas/ui/transaction'; -import { getRenderedHref } from '../../../../../utils/testHelpers'; -import { DiscoverErrorLink } from '../DiscoverErrorLink'; -import { DiscoverSpanLink } from '../DiscoverSpanLink'; -import { DiscoverTransactionLink } from '../DiscoverTransactionLink'; +import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; +import { Span } from '../../../../../typings/es_schemas/ui/span'; +import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; +import { getRenderedHref } from '../../../../utils/testHelpers'; +import { DiscoverErrorLink } from './DiscoverErrorLink'; +import { DiscoverSpanLink } from './DiscoverSpanLink'; +import { DiscoverTransactionLink } from './DiscoverTransactionLink'; describe('DiscoverLinks', () => { it('produces the correct URL for a transaction', async () => { diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverTransactionLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverTransactionLink.test.tsx similarity index 84% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverTransactionLink.test.tsx rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverTransactionLink.test.tsx index 48d8bb2b416442..0ded3fb6619e33 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverTransactionLink.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverTransactionLink.test.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Transaction } from '../../../../../../typings/es_schemas/ui/transaction'; +import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; // @ts-expect-error import configureStore from '../../../../../store/config/configureStore'; -import { getDiscoverQuery } from '../DiscoverTransactionLink'; +import { getDiscoverQuery } from './DiscoverTransactionLink'; function getMockTransaction() { return { diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/mock_transaction.json b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__fixtures__/mock_transaction.json similarity index 100% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/mock_transaction.json rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__fixtures__/mock_transaction.json diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/DiscoverErrorButton.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/DiscoverErrorButton.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/DiscoverErrorButton.test.tsx.snap rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/DiscoverErrorButton.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/DiscoverErrorLink.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/DiscoverErrorLink.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/DiscoverErrorLink.test.tsx.snap rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/DiscoverErrorLink.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/DiscoverTransactionLink.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/DiscoverTransactionLink.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/DiscoverTransactionLink.test.tsx.snap rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/DiscoverTransactionLink.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/discover_transaction_button.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/discover_transaction_button.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/__snapshots__/discover_transaction_button.test.tsx.snap rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__snapshots__/discover_transaction_button.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/discover_transaction_button.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/discover_transaction_button.test.tsx similarity index 82% rename from x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/discover_transaction_button.test.tsx rename to x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/discover_transaction_button.test.tsx index 4a68a5c0b4904a..75fe18913618d4 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/discover_transaction_button.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/discover_transaction_button.test.tsx @@ -6,12 +6,12 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Transaction } from '../../../../../../typings/es_schemas/ui/transaction'; +import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { DiscoverTransactionLink, getDiscoverQuery, -} from '../DiscoverTransactionLink'; -import mockTransaction from './mock_transaction.json'; +} from './DiscoverTransactionLink'; +import mockTransaction from './__fixtures__/mock_transaction.json'; describe('DiscoverTransactionLink component', () => { it('should render with data', () => { diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js b/x-pack/plugins/apm/public/components/shared/ManagedTable/ManagedTable.test.js similarity index 96% rename from x-pack/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js rename to x-pack/plugins/apm/public/components/shared/ManagedTable/ManagedTable.test.js index 38f260b04e2521..88e1c57e623547 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/ManagedTable.test.js @@ -6,7 +6,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { UnoptimizedManagedTable } from '..'; +import { UnoptimizedManagedTable } from '.'; describe('ManagedTable component', () => { let people; diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/__test__/__snapshots__/ManagedTable.test.js.snap b/x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/ManagedTable/__test__/__snapshots__/ManagedTable.test.js.snap rename to x-pack/plugins/apm/public/components/shared/ManagedTable/__snapshots__/ManagedTable.test.js.snap diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/ErrorMetadata.test.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx rename to x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/ErrorMetadata.test.tsx index 8f44d98cecdf7b..8a50bc2cde5203 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/ErrorMetadata.test.tsx @@ -7,13 +7,13 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { ErrorMetadata } from '..'; -import { APMError } from '../../../../../../typings/es_schemas/ui/apm_error'; -import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { ErrorMetadata } from '.'; +import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, -} from '../../../../../utils/testHelpers'; +} from '../../../../utils/testHelpers'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/MetadataTable.test.tsx similarity index 87% rename from x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx rename to x-pack/plugins/apm/public/components/shared/MetadataTable/MetadataTable.test.tsx index 8a4cd588c82600..9bd3278033f928 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/MetadataTable.test.tsx @@ -7,10 +7,10 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MetadataTable } from '..'; -import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; -import { expectTextsInDocument } from '../../../../utils/testHelpers'; -import { SectionsWithRows } from '../helper'; +import { MetadataTable } from '.'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { expectTextsInDocument } from '../../../utils/testHelpers'; +import { SectionsWithRows } from './helper'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/Section.test.tsx similarity index 83% rename from x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx rename to x-pack/plugins/apm/public/components/shared/MetadataTable/Section.test.tsx index 7a150f81580d86..3dd19778430b78 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/Section.test.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; import { render } from '@testing-library/react'; -import { Section } from '../Section'; -import { expectTextsInDocument } from '../../../../utils/testHelpers'; +import { Section } from './Section'; +import { expectTextsInDocument } from '../../../utils/testHelpers'; describe('Section', () => { it('shows "empty state message" if no data is available', () => { diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/SpanMetadata.test.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx rename to x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/SpanMetadata.test.tsx index c97e5061873478..c9ed2c4c2b32f3 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/SpanMetadata.test.tsx @@ -7,13 +7,13 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { SpanMetadata } from '..'; -import { Span } from '../../../../../../typings/es_schemas/ui/span'; -import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { SpanMetadata } from '.'; +import { Span } from '../../../../../typings/es_schemas/ui/span'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, -} from '../../../../../utils/testHelpers'; +} from '../../../../utils/testHelpers'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/TransactionMetadata.test.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx rename to x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/TransactionMetadata.test.tsx index 4080a300ba17f0..6a5a122f23954a 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/TransactionMetadata.test.tsx @@ -7,13 +7,13 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { TransactionMetadata } from '..'; -import { Transaction } from '../../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { TransactionMetadata } from '.'; +import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, -} from '../../../../../utils/testHelpers'; +} from '../../../../utils/testHelpers'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts b/x-pack/plugins/apm/public/components/shared/MetadataTable/helper.test.ts similarity index 92% rename from x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts rename to x-pack/plugins/apm/public/components/shared/MetadataTable/helper.test.ts index ac776e0b8980c1..8f3e675c7aeaec 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/helper.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getSectionsWithRows, filterSectionsByTerm } from '../helper'; -import { LABELS, HTTP, SERVICE } from '../sections'; -import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; +import { getSectionsWithRows, filterSectionsByTerm } from './helper'; +import { LABELS, HTTP, SERVICE } from './sections'; +import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; describe('MetadataTable Helper', () => { const sections = [ diff --git a/x-pack/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.test.tsx similarity index 86% rename from x-pack/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx rename to x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.test.tsx index 26087e1fd85cc6..fd531f79c9ac6a 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.test.tsx @@ -5,11 +5,11 @@ */ import React from 'react'; -import { ErrorCountSummaryItemBadge } from '../ErrorCountSummaryItemBadge'; +import { ErrorCountSummaryItemBadge } from './ErrorCountSummaryItemBadge'; import { expectTextsInDocument, renderWithTheme, -} from '../../../../utils/testHelpers'; +} from '../../../utils/testHelpers'; describe('ErrorCountSummaryItemBadge', () => { it('shows singular error message', () => { diff --git a/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/__test__/HttpInfoSummaryItem.test.tsx b/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/HttpInfoSummaryItem.test.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/__test__/HttpInfoSummaryItem.test.tsx rename to x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/HttpInfoSummaryItem.test.tsx index d0e1f08aabbbce..9465d94e16dc86 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/__test__/HttpInfoSummaryItem.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/HttpInfoSummaryItem.test.tsx @@ -6,8 +6,8 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; -import { HttpInfoSummaryItem } from '../'; -import * as exampleTransactions from '../../__fixtures__/transactions'; +import { HttpInfoSummaryItem } from '.'; +import * as exampleTransactions from '../__fixtures__/transactions'; describe('HttpInfoSummaryItem', () => { describe('render', () => { diff --git a/x-pack/plugins/apm/public/components/shared/Summary/HttpStatusBadge/__test__/HttpStatusBadge.test.tsx b/x-pack/plugins/apm/public/components/shared/Summary/HttpStatusBadge/HttpStatusBadge.test.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/shared/Summary/HttpStatusBadge/__test__/HttpStatusBadge.test.tsx rename to x-pack/plugins/apm/public/components/shared/Summary/HttpStatusBadge/HttpStatusBadge.test.tsx index ecbf41486a3fd0..0df23883d31277 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/HttpStatusBadge/__test__/HttpStatusBadge.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/HttpStatusBadge/HttpStatusBadge.test.tsx @@ -6,13 +6,13 @@ import React from 'react'; import { mount } from 'enzyme'; -import { HttpStatusBadge } from '../index'; +import { HttpStatusBadge } from './index'; import { successColor, neutralColor, warningColor, errorColor, -} from '../../../../../utils/httpStatusCodeToColor'; +} from '../../../../utils/httpStatusCodeToColor'; describe('HttpStatusBadge', () => { describe('render', () => { diff --git a/x-pack/plugins/apm/public/components/shared/TimestampTooltip/__test__/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/shared/TimestampTooltip/__test__/index.test.tsx rename to x-pack/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx index b4678b287dc166..dd36827ea94f21 100644 --- a/x-pack/plugins/apm/public/components/shared/TimestampTooltip/__test__/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx @@ -7,8 +7,8 @@ import { shallow } from 'enzyme'; import React from 'react'; import moment from 'moment-timezone'; -import { TimestampTooltip } from '../index'; -import { mockNow } from '../../../../utils/testHelpers'; +import { TimestampTooltip } from './index'; +import { mockNow } from '../../../utils/testHelpers'; describe('TimestampTooltip', () => { const timestamp = 1570720000123; // Oct 10, 2019, 08:06:40.123 (UTC-7) diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx rename to x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx index 8cb863c8fc3852..6ff395db594f13 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx @@ -7,18 +7,18 @@ import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { License } from '../../../../../../licensing/common/license'; -import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; -import { LicenseContext } from '../../../../context/license/license_context'; -import * as hooks from '../../../../hooks/use_fetcher'; -import * as apmApi from '../../../../services/rest/createCallApmApi'; +import { License } from '../../../../../licensing/common/license'; +import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { LicenseContext } from '../../../context/license/license_context'; +import * as hooks from '../../../hooks/use_fetcher'; +import * as apmApi from '../../../services/rest/createCallApmApi'; import { expectTextsInDocument, expectTextsNotInDocument, -} from '../../../../utils/testHelpers'; -import { TransactionActionMenu } from '../TransactionActionMenu'; -import * as Transactions from './mockData'; +} from '../../../utils/testHelpers'; +import { TransactionActionMenu } from './TransactionActionMenu'; +import * as Transactions from './__fixtures__/mockData'; function Wrapper({ children }: { children?: React.ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/mockData.ts b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__fixtures__/mockData.ts similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/mockData.ts rename to x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__fixtures__/mockData.ts diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__snapshots__/TransactionActionMenu.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap rename to x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__snapshots__/TransactionActionMenu.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/sections.test.ts b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.test.ts similarity index 98% rename from x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/sections.test.ts rename to x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.test.ts index 048ae9474c4030..f6067a34e2b904 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/sections.test.ts +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.test.ts @@ -5,8 +5,8 @@ */ import { Location } from 'history'; import { IBasePath } from 'kibana/public'; -import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { getSections } from '../sections'; +import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; +import { getSections } from './sections'; describe('Transaction action menu', () => { const basePath = ({ diff --git a/x-pack/plugins/apm/public/services/__test__/SessionStorageMock.ts b/x-pack/plugins/apm/public/services/__mocks__/SessionStorageMock.ts similarity index 100% rename from x-pack/plugins/apm/public/services/__test__/SessionStorageMock.ts rename to x-pack/plugins/apm/public/services/__mocks__/SessionStorageMock.ts diff --git a/x-pack/plugins/apm/public/services/__test__/callApi.test.ts b/x-pack/plugins/apm/public/services/callApi.test.ts similarity index 97% rename from x-pack/plugins/apm/public/services/__test__/callApi.test.ts rename to x-pack/plugins/apm/public/services/callApi.test.ts index f82201bbd4de81..1e606ac4b9aa94 100644 --- a/x-pack/plugins/apm/public/services/__test__/callApi.test.ts +++ b/x-pack/plugins/apm/public/services/callApi.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mockNow } from '../../utils/testHelpers'; -import { clearCache, callApi } from '../rest/callApi'; -import { SessionStorageMock } from './SessionStorageMock'; +import { mockNow } from '../utils/testHelpers'; +import { clearCache, callApi } from './rest/callApi'; +import { SessionStorageMock } from './__mocks__/SessionStorageMock'; import { HttpSetup } from 'kibana/public'; type HttpMock = HttpSetup & { diff --git a/x-pack/plugins/apm/public/services/__test__/callApmApi.test.ts b/x-pack/plugins/apm/public/services/callApmApi.test.ts similarity index 93% rename from x-pack/plugins/apm/public/services/__test__/callApmApi.test.ts rename to x-pack/plugins/apm/public/services/callApmApi.test.ts index 2307ec9f06bb5f..5906053cbd8104 100644 --- a/x-pack/plugins/apm/public/services/__test__/callApmApi.test.ts +++ b/x-pack/plugins/apm/public/services/callApmApi.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as callApiExports from '../rest/callApi'; -import { createCallApmApi, callApmApi } from '../rest/createCallApmApi'; +import * as callApiExports from './rest/callApi'; +import { createCallApmApi, callApmApi } from './rest/createCallApmApi'; import { HttpSetup } from 'kibana/public'; const callApi = jest diff --git a/x-pack/plugins/apm/public/utils/__test__/flattenObject.test.ts b/x-pack/plugins/apm/public/utils/flattenObject.test.ts similarity index 96% rename from x-pack/plugins/apm/public/utils/__test__/flattenObject.test.ts rename to x-pack/plugins/apm/public/utils/flattenObject.test.ts index a71ecf73bad3fe..68f77573949eae 100644 --- a/x-pack/plugins/apm/public/utils/__test__/flattenObject.test.ts +++ b/x-pack/plugins/apm/public/utils/flattenObject.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { flattenObject } from '../flattenObject'; +import { flattenObject } from './flattenObject'; describe('FlattenObject', () => { it('flattens multi level item', () => { diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap b/x-pack/plugins/apm/server/lib/errors/distribution/__snapshots__/get_buckets.test.ts.snap similarity index 100% rename from x-pack/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap rename to x-pack/plugins/apm/server/lib/errors/distribution/__snapshots__/get_buckets.test.ts.snap diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts similarity index 92% rename from x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts rename to x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts index ff7d05efc1802f..e05e7d3df28289 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getBuckets } from '../get_buckets'; -import { APMConfig } from '../../../..'; -import { ProcessorEvent } from '../../../../../common/processor_event'; +import { getBuckets } from './get_buckets'; +import { APMConfig } from '../../..'; +import { ProcessorEvent } from '../../../../common/processor_event'; describe('get buckets', () => { let clientSpy: jest.Mock; diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts similarity index 80% rename from x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts rename to x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts index a319bba1eabe1f..711790d0c4aaed 100644 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getEnvironmentUiFilterES } from '../get_environment_ui_filter_es'; -import { ENVIRONMENT_NOT_DEFINED } from '../../../../../common/environment_filter_values'; -import { SERVICE_ENVIRONMENT } from '../../../../../common/elasticsearch_fieldnames'; +import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; +import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values'; +import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; describe('getEnvironmentUiFilterES', () => { it('should return empty array, when environment is undefined', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/canvas_addons.ts b/x-pack/plugins/canvas/canvas_plugin_src/canvas_addons.ts new file mode 100644 index 00000000000000..fe5e1d7e5f9838 --- /dev/null +++ b/x-pack/plugins/canvas/canvas_plugin_src/canvas_addons.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-expect-error Untyped Local +export * from './uis/datasources'; +export * from './elements'; +// @ts-expect-error Untyped Local +export * from './uis/models'; +export * from './uis/views'; +export * from './uis/arguments'; +export * from './uis/tags'; +// @ts-expect-error Untyped Local +export * from './uis/transforms'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts index 7ecebd6d0677a9..e650cd50371184 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts @@ -14,17 +14,6 @@ import { Start as InspectorStart } from '../../../../src/plugins/inspector/publi import { functions } from './functions/browser'; import { typeFunctions } from './expression_types'; import { renderFunctions, renderFunctionFactories } from './renderers'; -import { initializeElements } from './elements'; -// @ts-expect-error untyped local -import { transformSpecs } from './uis/transforms'; -// @ts-expect-error untyped local -import { datasourceSpecs } from './uis/datasources'; -// @ts-expect-error untyped local -import { modelSpecs } from './uis/models'; -import { initializeViews } from './uis/views'; -import { initializeArgs } from './uis/arguments'; -import { tagSpecs } from './uis/tags'; - interface SetupDeps { canvas: CanvasSetup; } @@ -53,13 +42,44 @@ export class CanvasSrcPlugin implements Plugin ); }); - plugins.canvas.addElements(initializeElements(core, plugins)); - plugins.canvas.addDatasourceUIs(datasourceSpecs); - plugins.canvas.addModelUIs(modelSpecs); - plugins.canvas.addViewUIs(initializeViews(core, plugins)); - plugins.canvas.addArgumentUIs(initializeArgs(core, plugins)); - plugins.canvas.addTagUIs(tagSpecs); - plugins.canvas.addTransformUIs(transformSpecs); + plugins.canvas.addDatasourceUIs(async () => { + // @ts-expect-error + const { datasourceSpecs } = await import('./canvas_addons'); + return datasourceSpecs; + }); + + plugins.canvas.addElements(async () => { + const { initializeElements } = await import('./canvas_addons'); + return initializeElements(core, plugins); + }); + + plugins.canvas.addModelUIs(async () => { + // @ts-expect-error Untyped local + const { modelSpecs } = await import('./canvas_addons'); + return modelSpecs; + }); + + plugins.canvas.addViewUIs(async () => { + const { initializeViews } = await import('./canvas_addons'); + + return initializeViews(core, plugins); + }); + + plugins.canvas.addArgumentUIs(async () => { + const { initializeArgs } = await import('./canvas_addons'); + return initializeArgs(core, plugins); + }); + + plugins.canvas.addTagUIs(async () => { + const { tagSpecs } = await import('./canvas_addons'); + return tagSpecs; + }); + + plugins.canvas.addTransformUIs(async () => { + // @ts-expect-error Untyped local + const { transformSpecs } = await import('./canvas_addons'); + return transformSpecs; + }); } public start(core: CoreStart, plugins: StartDeps) {} diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx index 7d65a99b1dd453..fc02df3740cdb3 100644 --- a/x-pack/plugins/canvas/public/application.tsx +++ b/x-pack/plugins/canvas/public/application.tsx @@ -103,7 +103,7 @@ export const initializeCanvas = async ( // Init Registries initRegistries(); - populateRegistries(registries); + await populateRegistries(registries); // Set Badge coreStart.chrome.setBadge( diff --git a/x-pack/plugins/canvas/public/components/expression/expression.js b/x-pack/plugins/canvas/public/components/expression/expression.tsx similarity index 86% rename from x-pack/plugins/canvas/public/components/expression/expression.js rename to x-pack/plugins/canvas/public/components/expression/expression.tsx index 37cf1b821d9fd7..141963d479724f 100644 --- a/x-pack/plugins/canvas/public/components/expression/expression.js +++ b/x-pack/plugins/canvas/public/components/expression/expression.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FC, MutableRefObject } from 'react'; import PropTypes from 'prop-types'; import { EuiPanel, @@ -16,19 +16,26 @@ import { EuiLink, EuiPortal, } from '@elastic/eui'; +// @ts-expect-error import { Shortcuts } from 'react-shortcuts'; import { ComponentStrings } from '../../../i18n'; import { ExpressionInput } from '../expression_input'; import { ToolTipShortcut } from '../tool_tip_shortcut'; +import { ExpressionFunction } from '../../../types'; +import { FormState } from './'; const { Expression: strings } = ComponentStrings; const { useRef } = React; -const shortcut = (ref, cmd, callback) => ( +const shortcut = ( + ref: MutableRefObject, + cmd: string, + callback: () => void +) => ( { + handler={(command: string) => { const isInputActive = ref.current && ref.current.editor && ref.current.editor.hasTextFocus(); if (isInputActive && command === cmd) { callback(); @@ -40,18 +47,28 @@ const shortcut = (ref, cmd, callback) => ( /> ); -export const Expression = ({ +interface Props { + functionDefinitions: ExpressionFunction[]; + formState: FormState; + updateValue: (expression?: string) => void; + setExpression: (expression: string) => void; + done: () => void; + error?: string; + isCompact: boolean; + toggleCompactView: () => void; +} + +export const Expression: FC = ({ functionDefinitions, formState, updateValue, setExpression, done, error, - fontSize, isCompact, toggleCompactView, }) => { - const refExpressionInput = useRef(null); + const refExpressionInput = useRef(null); const handleRun = () => { setExpression(formState.expression); @@ -78,7 +95,6 @@ export const Expression = ({ ({ - pageId: getSelectedPage(state), - element: getSelectedElement(state), -}); - -const mapDispatchToProps = (dispatch) => ({ - setExpression: (elementId, pageId) => (expression) => { - // destroy the context cache - dispatch(flushContext(elementId)); - - // update the element's expression - dispatch(setExpression(expression, elementId, pageId)); - }, -}); - -const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { element, pageId } = stateProps; - const allProps = { ...ownProps, ...stateProps, ...dispatchProps }; - - if (!element) { - return allProps; - } - - const { expression } = element; - - const functions = Object.values(allProps.services.expressions.getFunctions()); - - return { - ...allProps, - expression, - functionDefinitions: functions, - setExpression: dispatchProps.setExpression(element.id, pageId), - }; -}; - -const expressionLifecycle = lifecycle({ - componentDidUpdate({ expression }) { - if ( - this.props.expression !== expression && - this.props.expression !== this.props.formState.expression - ) { - this.props.setFormState({ - expression: this.props.expression, - dirty: false, - }); - } - }, -}); - -export const Expression = compose( - withServices, - connect(mapStateToProps, mapDispatchToProps, mergeProps), - withState('formState', 'setFormState', ({ expression }) => ({ - expression, - dirty: false, - })), - withState('isCompact', 'setCompact', true), - withHandlers({ - toggleCompactView: ({ isCompact, setCompact }) => () => { - setCompact(!isCompact); - }, - updateValue: ({ setFormState }) => (expression) => { - setFormState({ - expression, - dirty: true, - }); - }, - setExpression: ({ setExpression, setFormState }) => (exp) => { - setFormState((prev) => ({ - ...prev, - dirty: false, - })); - setExpression(exp); - }, - }), - expressionLifecycle, - withPropsOnChange(['formState'], ({ formState }) => ({ - error: (function () { - try { - // TODO: We should merge the advanced UI input and this into a single validated expression input. - fromExpression(formState.expression); - return null; - } catch (e) { - return e.message; - } - })(), - })), - branch((props) => !props.element, renderComponent(ElementNotSelected)) -)(Component); diff --git a/x-pack/plugins/canvas/public/components/expression/index.tsx b/x-pack/plugins/canvas/public/components/expression/index.tsx new file mode 100644 index 00000000000000..fc4f1958ecb335 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/expression/index.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useState, useCallback, useMemo, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fromExpression } from '@kbn/interpreter/common'; +import { useServices } from '../../services'; +import { getSelectedPage, getSelectedElement } from '../../state/selectors/workpad'; +// @ts-expect-error +import { setExpression, flushContext } from '../../state/actions/elements'; +// @ts-expect-error +import { ElementNotSelected } from './element_not_selected'; +import { Expression as Component } from './expression'; +import { State, CanvasElement } from '../../../types'; + +interface ExpressionProps { + done: () => void; +} + +interface ExpressionContainerProps extends ExpressionProps { + element: CanvasElement; + pageId: string; +} + +export interface FormState { + dirty: boolean; + expression: string; +} + +export const Expression: FC = ({ done }) => { + const { element, pageId } = useSelector((state: State) => ({ + pageId: getSelectedPage(state), + element: getSelectedElement(state), + })); + + if (!element) { + return ; + } + + return ; +}; + +const ExpressionContainer: FC = ({ done, element, pageId }) => { + const services = useServices(); + const dispatch = useDispatch(); + const [isCompact, setCompact] = useState(true); + const toggleCompactView = useCallback(() => { + setCompact(!isCompact); + }, [isCompact, setCompact]); + + const dispatchSetExpression = useCallback( + (expression: string) => { + // destroy the context cache + dispatch(flushContext(element.id)); + + // update the element's expression + dispatch(setExpression(expression, element.id, pageId)); + }, + [dispatch, element, pageId] + ); + + const [formState, setFormState] = useState({ + dirty: false, + expression: element.expression, + }); + + const updateValue = useCallback( + (expression: string = '') => { + setFormState({ + expression, + dirty: true, + }); + }, + [setFormState] + ); + + const onSetExpression = useCallback( + (expression: string) => { + setFormState({ + ...formState, + dirty: false, + }); + dispatchSetExpression(expression); + }, + [setFormState, dispatchSetExpression, formState] + ); + + const currentExpression = formState.expression; + + const error = useMemo(() => { + try { + // TODO: We should merge the advanced UI input and this into a single validated expression input. + fromExpression(currentExpression); + return null; + } catch (e) { + return e.message; + } + }, [currentExpression]); + + useEffect(() => { + if (element.expression !== formState.expression && !formState.dirty) { + setFormState({ + dirty: false, + expression: element.expression, + }); + } + }, [element, setFormState, formState]); + + return ( + + ); +}; diff --git a/x-pack/plugins/canvas/public/components/expression_input/index.js b/x-pack/plugins/canvas/public/components/expression_input/index.ts similarity index 100% rename from x-pack/plugins/canvas/public/components/expression_input/index.js rename to x-pack/plugins/canvas/public/components/expression_input/index.ts diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx index 6905b3ed23d3ff..7151e72a44780c 100644 --- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx +++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx @@ -21,7 +21,6 @@ import { import { WorkpadManager } from '../workpad_manager'; import { RouterContext } from '../router'; import { PageManager } from '../page_manager'; -// @ts-expect-error untyped local import { Expression } from '../expression'; import { Tray } from './tray'; diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index d18f1b8d244899..3c6c0d68da3db9 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -26,9 +26,6 @@ import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { Start as InspectorStart } from '../../../../src/plugins/inspector/public'; import { BfetchPublicSetup } from '../../../../src/plugins/bfetch/public'; -// @ts-expect-error untyped local -import { argTypeSpecs } from './expression_types/arg_types'; -import { transitions } from './transitions'; import { getPluginApi, CanvasApi } from './plugin_api'; import { CanvasSrcPlugin } from '../canvas_plugin_src/plugin'; export { CoreStart, CoreSetup }; @@ -123,8 +120,15 @@ export class CanvasPlugin plugins.home.featureCatalogue.register(featureCatalogueEntry); } - canvasApi.addArgumentUIs(argTypeSpecs); - canvasApi.addTransitions(transitions); + canvasApi.addArgumentUIs(async () => { + // @ts-expect-error + const { argTypeSpecs } = await import('./expression_types/arg_types'); + return argTypeSpecs; + }); + canvasApi.addTransitions(async () => { + const { transitions } = await import('./transitions'); + return transitions; + }); return { ...canvasApi, diff --git a/x-pack/plugins/canvas/public/plugin_api.ts b/x-pack/plugins/canvas/public/plugin_api.ts index 62e82df4b0d044..be267bb91a9092 100644 --- a/x-pack/plugins/canvas/public/plugin_api.ts +++ b/x-pack/plugins/canvas/public/plugin_api.ts @@ -12,24 +12,26 @@ import { import { ElementFactory } from '../types'; import { ExpressionsSetup } from '../../../../src/plugins/expressions/public'; -type AddToRegistry = (add: T[]) => void; +type SpecPromiseFn = () => Promise; +type AddToRegistry = (add: T[] | SpecPromiseFn) => void; +type AddSpecsToRegistry = (add: T[]) => void; export interface CanvasApi { addArgumentUIs: AddToRegistry; addDatasourceUIs: AddToRegistry; addElements: AddToRegistry; - addFunctions: AddToRegistry<() => AnyExpressionFunctionDefinition>; + addFunctions: AddSpecsToRegistry<() => AnyExpressionFunctionDefinition>; addModelUIs: AddToRegistry; - addRenderers: AddToRegistry; + addRenderers: AddSpecsToRegistry; addTagUIs: AddToRegistry; addTransformUIs: AddToRegistry; addTransitions: AddToRegistry; - addTypes: AddToRegistry<() => AnyExpressionTypeDefinition>; + addTypes: AddSpecsToRegistry<() => AnyExpressionTypeDefinition>; addViewUIs: AddToRegistry; } -export interface SetupRegistries { - elements: ElementFactory[]; +export interface SetupRegistries extends Record { + elements: Array>; transformUIs: any[]; datasourceUIs: any[]; modelUIs: any[]; @@ -53,6 +55,16 @@ export function getPluginApi( transitions: [], }; + const addToRegistry = (registry: Array>) => { + return (entries: T[] | SpecPromiseFn) => { + if (Array.isArray(entries)) { + registry.push(...entries); + } else { + registry.push(entries); + } + }; + }; + const api: CanvasApi = { // Functions, types and renderers are registered directly to expression plugin addFunctions: (fns) => { @@ -75,14 +87,14 @@ export function getPluginApi( }, // All these others are local to canvas, and they will only register on start - addElements: (elements) => registries.elements.push(...elements), - addTransformUIs: (transforms) => registries.transformUIs.push(...transforms), - addDatasourceUIs: (datasources) => registries.datasourceUIs.push(...datasources), - addModelUIs: (models) => registries.modelUIs.push(...models), - addViewUIs: (views) => registries.viewUIs.push(...views), - addArgumentUIs: (args) => registries.argumentUIs.push(...args), - addTagUIs: (tags) => registries.tagUIs.push(...tags), - addTransitions: (transitions) => registries.transitions.push(...transitions), + addElements: addToRegistry(registries.elements), + addTransformUIs: addToRegistry(registries.transformUIs), + addDatasourceUIs: addToRegistry(registries.datasourceUIs), + addModelUIs: addToRegistry(registries.modelUIs), + addViewUIs: addToRegistry(registries.viewUIs), + addArgumentUIs: addToRegistry(registries.argumentUIs), + addTagUIs: addToRegistry(registries.tagUIs), + addTransitions: addToRegistry(registries.transitions), }; return { api, registries }; diff --git a/x-pack/plugins/canvas/public/registries.ts b/x-pack/plugins/canvas/public/registries.ts index b2881fc0b77999..5f87beb207b8c1 100644 --- a/x-pack/plugins/canvas/public/registries.ts +++ b/x-pack/plugins/canvas/public/registries.ts @@ -40,8 +40,24 @@ export function initRegistries() { }); } -export function populateRegistries(setupRegistries: SetupRegistries) { - register(registries, setupRegistries); +export async function populateRegistries(setupRegistries: SetupRegistries) { + // Our setup registries could contain definitions or a function that would + // return a promise of definitions. + // We need to call all the fns and then wait for all of the promises to be resolved + const resolvedRegistries: Record = {}; + const promises = Object.entries(setupRegistries).map(async ([key, specs]) => { + const resolved = await ( + await Promise.all(specs.map((fn) => (typeof fn === 'function' ? fn() : fn))) + ).flat(); + + resolvedRegistries[key] = resolved; + }); + + // Now, wait for all of the promise registry promises to resolve and our resolved registry will be ready + // and we can proceeed + await Promise.all(promises); + + register(registries, resolvedRegistries); } export function destroyRegistries() { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx index d9976605393c7a..a777f30fd2e42b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx @@ -52,7 +52,7 @@ export const HotPhase: FunctionComponent = () => { watch: isUsingDefaultRolloverPath, }); const { isUsingRollover } = useConfigurationIssues(); - const isUsingDefaultRollover = get(formData, isUsingDefaultRolloverPath); + const isUsingDefaultRollover: boolean = get(formData, isUsingDefaultRolloverPath); const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false); return ( @@ -145,145 +145,145 @@ export const HotPhase: FunctionComponent = () => { } fullWidth > -
- path="_meta.hot.useRollover"> - {(field) => ( - <> - field.setValue(e.target.checked)} - data-test-subj="rolloverSwitch" - /> -   - + path="_meta.hot.customRollover.enabled"> + {(field) => ( + <> + field.setValue(e.target.checked)} + data-test-subj="rolloverSwitch" + /> +   + + } + /> + + )} + + {isUsingRollover && ( + <> + + {showEmptyRolloverFieldsError && ( + <> + +
{i18nTexts.editPolicy.errors.rollOverConfigurationCallout.body}
+
+ + + )} + + + + {(field) => { + const showErrorCallout = field.errors.some( + (e) => e.code === ROLLOVER_EMPTY_VALIDATION + ); + if (showErrorCallout !== showEmptyRolloverFieldsError) { + setShowEmptyRolloverFieldsError(showErrorCallout); + } + return ( + + ); + }} + + + + - } - /> + + + + + + + + + + + + + + + + + )} - - {isUsingRollover && ( - <> - - {showEmptyRolloverFieldsError && ( - <> - -
{i18nTexts.editPolicy.errors.rollOverConfigurationCallout.body}
-
- - - )} - - - - {(field) => { - const showErrorCallout = field.errors.some( - (e) => e.code === ROLLOVER_EMPTY_VALIDATION - ); - if (showErrorCallout !== showEmptyRolloverFieldsError) { - setShowEmptyRolloverFieldsError(showErrorCallout); - } - return ( - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - )} -
+ + ) : ( +
+ )} {isUsingRollover && ( <> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts index 48ed38fc8a0d72..af59aa4b9323ac 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const useRolloverPath = '_meta.hot.useRollover'; +export const isUsingCustomRolloverPath = '_meta.hot.customRollover.enabled'; export const isUsingDefaultRolloverPath = '_meta.hot.isUsingDefaultRollover'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx index 3a66abebccc1a7..4ddb85899f3acd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx @@ -9,7 +9,7 @@ import React, { FunctionComponent, createContext, useContext } from 'react'; import { useFormData } from '../../../../shared_imports'; -import { isUsingDefaultRolloverPath, useRolloverPath } from '../constants'; +import { isUsingDefaultRolloverPath, isUsingCustomRolloverPath } from '../constants'; export interface ConfigurationIssues { /** @@ -33,14 +33,20 @@ const pathToHotPhaseSearchableSnapshot = export const ConfigurationIssuesProvider: FunctionComponent = ({ children }) => { const [formData] = useFormData({ - watch: [pathToHotPhaseSearchableSnapshot, useRolloverPath, isUsingDefaultRolloverPath], + watch: [ + pathToHotPhaseSearchableSnapshot, + isUsingCustomRolloverPath, + isUsingDefaultRolloverPath, + ], }); const isUsingDefaultRollover = get(formData, isUsingDefaultRolloverPath); - const rolloverSwitchEnabled = get(formData, useRolloverPath); + // Provide default value, as path may become undefined if removed from the DOM + const isUsingCustomRollover = get(formData, isUsingCustomRolloverPath, true); + return ( { const _meta: FormInternal['_meta'] = { hot: { - useRollover: Boolean(hot?.actions?.rollover), isUsingDefaultRollover: isUsingDefaultRollover(policy), + customRollover: { + enabled: Boolean(hot?.actions?.rollover), + }, bestCompression: hot?.actions?.forcemerge?.index_codec === 'best_compression', readonlyEnabled: Boolean(hot?.actions?.readonly), }, @@ -53,13 +55,13 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { if (draft.phases.hot.actions.rollover.max_size) { const maxSize = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_size); draft.phases.hot.actions.rollover.max_size = maxSize.size; - draft._meta.hot.maxStorageSizeUnit = maxSize.units; + draft._meta.hot.customRollover.maxStorageSizeUnit = maxSize.units; } if (draft.phases.hot.actions.rollover.max_age) { const maxAge = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_age); draft.phases.hot.actions.rollover.max_age = maxAge.size; - draft._meta.hot.maxAgeUnit = maxAge.units; + draft._meta.hot.customRollover.maxAgeUnit = maxAge.units; } } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts index d72dbb38f6c95c..b5abf51c29028a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts @@ -7,6 +7,7 @@ import { setAutoFreeze } from 'immer'; import { cloneDeep } from 'lodash'; import { SerializedPolicy } from '../../../../../common/types'; +import { defaultRolloverAction } from '../../../constants'; import { deserializer } from './deserializer'; import { createSerializer } from './serializer'; import { FormInternal } from '../types'; @@ -202,6 +203,18 @@ describe('deserializer and serializer', () => { expect(result.phases.warm!.actions.readonly).toBeUndefined(); }); + it('allows force merge and readonly actions to be configured in hot with default rollover enabled', () => { + formInternal._meta.hot.isUsingDefaultRollover = true; + formInternal._meta.hot.bestCompression = false; + formInternal.phases.hot!.actions.forcemerge = undefined; + formInternal._meta.hot.readonlyEnabled = false; + + const result = serializer(formInternal); + + expect(result.phases.hot!.actions.readonly).toBeUndefined(); + expect(result.phases.hot!.actions.forcemerge).toBeUndefined(); + }); + it('removes set priority if it is disabled in the form', () => { delete formInternal.phases.hot!.actions.set_priority; delete formInternal.phases.warm!.actions.set_priority; @@ -234,17 +247,21 @@ describe('deserializer and serializer', () => { expect(result.phases.cold!.actions.allocate!.exclude).toBeUndefined(); }); - it('removes forcemerge and rollover config when rollover is disabled in hot phase', () => { - formInternal._meta.hot.useRollover = false; + it('removes forcemerge, readonly, and rollover config when rollover is disabled in hot phase', () => { + // These two toggles jointly control whether rollover is enabled since the default is + // for rollover to be enabled. + formInternal._meta.hot.isUsingDefaultRollover = false; + formInternal._meta.hot.customRollover.enabled = false; const result = serializer(formInternal); expect(result.phases.hot!.actions.rollover).toBeUndefined(); expect(result.phases.hot!.actions.forcemerge).toBeUndefined(); + expect(result.phases.hot!.actions.readonly).toBeUndefined(); }); it('removes min_age from warm when rollover is enabled', () => { - formInternal._meta.hot.useRollover = true; + formInternal._meta.hot.customRollover.enabled = true; formInternal._meta.warm.warmPhaseOnRollover = true; const result = serializer(formInternal); @@ -252,6 +269,15 @@ describe('deserializer and serializer', () => { expect(result.phases.warm!.min_age).toBeUndefined(); }); + it('adds default rollover configuration when enabled, but previously not configured', () => { + delete formInternal.phases.hot!.actions.rollover; + formInternal._meta.hot.isUsingDefaultRollover = true; + + const result = serializer(formInternal); + + expect(result.phases.hot!.actions.rollover).toEqual(defaultRolloverAction); + }); + it('removes snapshot_repository when it is unset', () => { delete formInternal.phases.hot!.actions.searchable_snapshot; delete formInternal.phases.cold!.actions.searchable_snapshot; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index ae2432971059c6..4bdf902d27b6d1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -32,23 +32,25 @@ const serializers = { export const schema: FormSchema = { _meta: { hot: { - useRollover: { - defaultValue: true, - label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.enableRolloverLabel', { - defaultMessage: 'Enable rollover', - }), - }, isUsingDefaultRollover: { defaultValue: true, label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.isUsingDefaultRollover', { defaultMessage: 'Use recommended defaults', }), }, - maxStorageSizeUnit: { - defaultValue: 'gb', - }, - maxAgeUnit: { - defaultValue: 'd', + customRollover: { + enabled: { + defaultValue: true, + label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.enableRolloverLabel', { + defaultMessage: 'Enable rollover', + }), + }, + maxStorageSizeUnit: { + defaultValue: 'gb', + }, + maxAgeUnit: { + defaultValue: 'd', + }, }, bestCompression: { label: i18nTexts.editPolicy.bestCompressionFieldLabel, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts index 2a7c109fec9503..f718073afa3522 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts @@ -5,7 +5,6 @@ */ import { produce } from 'immer'; - import { merge, cloneDeep } from 'lodash'; import { SerializedPolicy } from '../../../../../../common/types'; @@ -29,6 +28,13 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( // Copy over all updated fields merge(draft, updatedPolicy); + /** + * Important shared values for serialization + */ + const isUsingRollover = Boolean( + _meta.hot.isUsingDefaultRollover || _meta.hot.customRollover.enabled + ); + // Next copy over all meta fields and delete any fields that have been removed // by fields exposed in the form. It is very important that we do not delete // data that the form does not control! E.g., unfollow action in hot phase. @@ -42,25 +48,40 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( if (draft.phases.hot?.actions) { const hotPhaseActions = draft.phases.hot.actions; - if (_meta.hot.isUsingDefaultRollover) { - hotPhaseActions.rollover = cloneDeep(defaultRolloverAction); - } else if (hotPhaseActions.rollover && _meta.hot.useRollover) { - if (updatedPolicy.phases.hot!.actions.rollover?.max_age) { - hotPhaseActions.rollover.max_age = `${hotPhaseActions.rollover.max_age}${_meta.hot.maxAgeUnit}`; - } else { - delete hotPhaseActions.rollover.max_age; - } - if (typeof updatedPolicy.phases.hot!.actions.rollover?.max_docs !== 'number') { - delete hotPhaseActions.rollover.max_docs; - } - - if (updatedPolicy.phases.hot!.actions.rollover?.max_size) { - hotPhaseActions.rollover.max_size = `${hotPhaseActions.rollover.max_size}${_meta.hot.maxStorageSizeUnit}`; + /** + * HOT PHASE ROLLOVER + */ + if (isUsingRollover) { + if (_meta.hot.isUsingDefaultRollover) { + hotPhaseActions.rollover = cloneDeep(defaultRolloverAction); } else { - delete hotPhaseActions.rollover.max_size; + // Rollover may not exist if editing an existing policy with initially no rollover configured + if (!hotPhaseActions.rollover) { + hotPhaseActions.rollover = {}; + } + + // We are using user-defined, custom rollover settings. + if (updatedPolicy.phases.hot!.actions.rollover?.max_age) { + hotPhaseActions.rollover.max_age = `${hotPhaseActions.rollover.max_age}${_meta.hot.customRollover.maxAgeUnit}`; + } else { + delete hotPhaseActions.rollover.max_age; + } + + if (typeof updatedPolicy.phases.hot!.actions.rollover?.max_docs !== 'number') { + delete hotPhaseActions.rollover.max_docs; + } + + if (updatedPolicy.phases.hot!.actions.rollover?.max_size) { + hotPhaseActions.rollover.max_size = `${hotPhaseActions.rollover.max_size}${_meta.hot.customRollover.maxStorageSizeUnit}`; + } else { + delete hotPhaseActions.rollover.max_size; + } } + /** + * HOT PHASE FORCEMERGE + */ if (!updatedPolicy.phases.hot!.actions?.forcemerge) { delete hotPhaseActions.forcemerge; } else if (_meta.hot.bestCompression) { @@ -73,6 +94,9 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( hotPhaseActions.forcemerge.index_codec = 'best_compression'; } + /** + * HOT PHASE READ-ONLY + */ if (_meta.hot.readonlyEnabled) { hotPhaseActions.readonly = hotPhaseActions.readonly ?? {}; } else { @@ -84,14 +108,23 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( delete hotPhaseActions.readonly; } + /** + * HOT PHASE SET PRIORITY + */ if (!updatedPolicy.phases.hot!.actions?.set_priority) { delete hotPhaseActions.set_priority; } + /** + * HOT PHASE SHRINK + */ if (!updatedPolicy.phases.hot?.actions?.shrink) { delete hotPhaseActions.shrink; } + /** + * HOT PHASE SEARCHABLE SNAPSHOT + */ if (!updatedPolicy.phases.hot!.actions?.searchable_snapshot) { delete hotPhaseActions.searchable_snapshot; } @@ -103,11 +136,16 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( if (_meta.warm.enabled) { draft.phases.warm!.actions = draft.phases.warm?.actions ?? {}; const warmPhase = draft.phases.warm!; - // If warm phase on rollover is enabled, delete min age field - // An index lifecycle switches to warm phase when rollover occurs, so you cannot specify a warm phase time - // They are mutually exclusive + + /** + * WARM PHASE MIN AGE + * + * If warm phase on rollover is enabled, delete min age field + * An index lifecycle switches to warm phase when rollover occurs, so you cannot specify a warm phase time + * They are mutually exclusive + */ if ( - (!_meta.hot.useRollover || !_meta.warm.warmPhaseOnRollover) && + (!isUsingRollover || !_meta.warm.warmPhaseOnRollover) && updatedPolicy.phases.warm?.min_age ) { warmPhase.min_age = `${updatedPolicy.phases.warm!.min_age}${_meta.warm.minAgeUnit}`; @@ -115,6 +153,9 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( delete warmPhase.min_age; } + /** + * WARM PHASE DATA ALLOCATION + */ warmPhase.actions = serializeMigrateAndAllocateActions( _meta.warm, warmPhase.actions, @@ -122,6 +163,9 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( updatedPolicy.phases.warm?.actions?.allocate?.number_of_replicas ); + /** + * WARM PHASE FORCEMERGE + */ if (!updatedPolicy.phases.warm?.actions?.forcemerge) { delete warmPhase.actions.forcemerge; } else if (_meta.warm.bestCompression) { @@ -130,16 +174,25 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( delete warmPhase.actions.forcemerge!.index_codec; } + /** + * WARM PHASE READ ONLY + */ if (_meta.warm.readonlyEnabled) { warmPhase.actions.readonly = warmPhase.actions.readonly ?? {}; } else { delete warmPhase.actions.readonly; } + /** + * WARM PHASE SET PRIORITY + */ if (!updatedPolicy.phases.warm?.actions?.set_priority) { delete warmPhase.actions.set_priority; } + /** + * WARM PHASE SHRINK + */ if (!updatedPolicy.phases.warm?.actions?.shrink) { delete warmPhase.actions.shrink; } @@ -154,10 +207,16 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( draft.phases.cold!.actions = draft.phases.cold?.actions ?? {}; const coldPhase = draft.phases.cold!; + /** + * COLD PHASE MIN AGE + */ if (updatedPolicy.phases.cold?.min_age) { coldPhase.min_age = `${updatedPolicy.phases.cold!.min_age}${_meta.cold.minAgeUnit}`; } + /** + * COLD PHASE DATA ALLOCATION + */ coldPhase.actions = serializeMigrateAndAllocateActions( _meta.cold, coldPhase.actions, @@ -165,16 +224,25 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( updatedPolicy.phases.cold?.actions?.allocate?.number_of_replicas ); + /** + * COLD PHASE FREEZE + */ if (_meta.cold.freezeEnabled) { coldPhase.actions.freeze = coldPhase.actions.freeze ?? {}; } else { delete coldPhase.actions.freeze; } + /** + * COLD PHASE SET PRIORITY + */ if (!updatedPolicy.phases.cold?.actions?.set_priority) { delete coldPhase.actions.set_priority; } + /** + * COLD PHASE SEARCHABLE SNAPSHOT + */ if (!updatedPolicy.phases.cold?.actions?.searchable_snapshot) { delete coldPhase.actions.searchable_snapshot; } @@ -187,12 +255,23 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( */ if (_meta.delete.enabled) { const deletePhase = draft.phases.delete!; + + /** + * DELETE PHASE DELETE + */ deletePhase.actions = deletePhase.actions ?? {}; deletePhase.actions.delete = deletePhase.actions.delete ?? {}; + + /** + * DELETE PHASE SEARCHABLE SNAPSHOT + */ if (updatedPolicy.phases.delete?.min_age) { deletePhase.min_age = `${updatedPolicy.phases.delete!.min_age}${_meta.delete.minAgeUnit}`; } + /** + * DELETE PHASE WAIT FOR SNAPSHOT + */ if (!updatedPolicy.phases.delete?.actions?.wait_for_snapshot) { delete deletePhase.actions.wait_for_snapshot; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 4dfd7503b99736..247f6071062162 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -22,11 +22,23 @@ export interface ForcemergeFields { } interface HotPhaseMetaFields extends ForcemergeFields { - useRollover: boolean; + /** + * By default rollover is enabled with set values for max age, max size and max docs. In this policy form + * opting in to default rollover overrides custom rollover values. + */ isUsingDefaultRollover: boolean; - maxStorageSizeUnit?: string; - maxAgeUnit?: string; + readonlyEnabled: boolean; + + /** + * If a policy has defined values other than the default rollover {@link defaultRolloverAction}, we store + * them here. + */ + customRollover: { + enabled: boolean; + maxStorageSizeUnit?: string; + maxAgeUnit?: string; + }; } interface WarmPhaseMetaFields extends DataAllocationMetaFields, MinAgeField, ForcemergeFields { diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json index 5dcff0ba942e12..af3d61c8808efe 100644 --- a/x-pack/plugins/index_management/kibana.json +++ b/x-pack/plugins/index_management/kibana.json @@ -9,6 +9,6 @@ "requiredBundles": [ "kibanaReact", "esUiShared", - "runtimeFields" + "runtimeFieldEditor" ] } diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts index 36f7fecbcff21b..652925a977fa0a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/shared_imports.ts @@ -58,7 +58,7 @@ export { RuntimeField, RuntimeFieldEditorFlyoutContent, RuntimeFieldEditorFlyoutContentProps, -} from '../../../../../runtime_fields/public'; +} from '../../../../../runtime_field_editor/public'; export { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index cc22cbbf57883d..1144a1043c5b1c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -30,6 +30,7 @@ import { updateColumnParam, resetIncomplete, FieldBasedIndexPatternColumn, + canTransition, } from '../operations'; import { mergeLayer } from '../state_helpers'; import { FieldSelect } from './field_select'; @@ -147,15 +148,20 @@ export function DimensionEditor(props: DimensionEditorProps) { const operationsWithCompatibility = [...possibleOperations].map((operationType) => { const definition = operationDefinitionMap[operationType]; + const currentField = + selectedColumn && + hasField(selectedColumn) && + currentIndexPattern.getFieldByName(selectedColumn.sourceField); return { operationType, - compatibleWithCurrentField: - !selectedColumn || - (selectedColumn && - hasField(selectedColumn) && - definition.input === 'field' && - fieldByOperation[operationType]?.has(selectedColumn.sourceField)) || - (selectedColumn && !hasField(selectedColumn) && definition.input === 'none'), + compatibleWithCurrentField: canTransition({ + layer: state.layers[layerId], + columnId, + op: operationType, + indexPattern: currentIndexPattern, + field: currentField || undefined, + filterOperations: props.filterOperations, + }), disabledStatus: definition.getDisabledStatus && definition.getDisabledStatus( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 5d477d98d042d5..fc6c3173658866 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -337,17 +337,124 @@ describe('IndexPatternDimensionEditorPanel', () => { const items: EuiListGroupItemProps[] = wrapper.find(EuiListGroup).prop('listItems') || []; - expect(items.find(({ label }) => label === 'Minimum')!['data-test-subj']).not.toContain( + expect(items.find(({ id }) => id === 'min')!['data-test-subj']).not.toContain('incompatible'); + expect(items.find(({ id }) => id === 'date_histogram')!['data-test-subj']).toContain( 'incompatible' ); + // Incompatible because there is no date field + expect(items.find(({ id }) => id === 'cumulative_sum')!['data-test-subj']).toContain( + 'incompatible' + ); + + expect(items.find(({ id }) => id === 'filters')!['data-test-subj']).not.toContain( + 'incompatible' + ); + }); + + it('should indicate when a transition is invalid due to filterOperations', () => { + wrapper = mount( + meta.dataType === 'number' && !meta.isBucketed} + /> + ); + + const items: EuiListGroupItemProps[] = wrapper.find(EuiListGroup).prop('listItems') || []; + + expect(items.find(({ id }) => id === 'min')!['data-test-subj']).toContain('incompatible'); + expect(items.find(({ id }) => id === 'cumulative_sum')!['data-test-subj']).toContain( + 'incompatible' + ); + }); + + it('should indicate that reference-based operations are not compatible when they are incomplete', () => { + wrapper = mount( + + ); + + const items: EuiListGroupItemProps[] = wrapper.find(EuiListGroup).prop('listItems') || []; + + expect(items.find(({ id }) => id === 'derivative')!['data-test-subj']).toContain( + 'incompatible' + ); + expect(items.find(({ id }) => id === 'cumulative_sum')!['data-test-subj']).toContain( + 'incompatible' + ); + expect(items.find(({ id }) => id === 'moving_average')!['data-test-subj']).toContain( + 'incompatible' + ); + }); - expect(items.find(({ label }) => label === 'Date histogram')!['data-test-subj']).toContain( + it('should indicate that reference-based operations are compatible sometimes', () => { + wrapper = mount( + + ); + + const items: EuiListGroupItemProps[] = wrapper.find(EuiListGroup).prop('listItems') || []; + + expect(items.find(({ id }) => id === 'counter_rate')!['data-test-subj']).toContain( 'incompatible' ); - // Fieldless operation is compatible with field - expect(items.find(({ label }) => label === 'Filters')!['data-test-subj']).toContain( - 'compatible' + expect(items.find(({ id }) => id === 'derivative')!['data-test-subj']).not.toContain( + 'incompatible' + ); + expect(items.find(({ id }) => id === 'moving_average')!['data-test-subj']).not.toContain( + 'incompatible' ); }); @@ -640,9 +747,7 @@ describe('IndexPatternDimensionEditorPanel', () => { .find('button[data-test-subj="lns-indexPatternDimension-terms incompatible"]') .simulate('click'); - wrapper - .find('button[data-test-subj="lns-indexPatternDimension-filters incompatible"]') - .simulate('click'); + wrapper.find('button[data-test-subj="lns-indexPatternDimension-filters"]').simulate('click'); expect(wrapper.find('[data-test-subj="indexPattern-invalid-operation"]')).toHaveLength(0); }); @@ -1623,7 +1728,15 @@ describe('IndexPatternDimensionEditorPanel', () => { id: '1', title: 'my-fake-index-pattern', hasRestrictions: false, - fields, + fields: [ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ], getFieldByName: getFieldByNameFactory([ { name: 'bytes', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts index 6d7a0117a17704..3d10080aea0c6f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts @@ -43,6 +43,7 @@ export const { isReferenced, resetIncomplete, isOperationAllowedAsReference, + canTransition, } = actualHelpers; export const { adjustTimeScaleLabelSuffix, DEFAULT_TIME_SCALE } = actualTimeScaleUtils; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts index ca4b7c53b7ec74..8058f0a2642298 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts @@ -10,8 +10,8 @@ import type { TimeScaleUnit } from '../../../time_scale'; import type { IndexPattern, IndexPatternLayer } from '../../../types'; import { adjustTimeScaleLabelSuffix } from '../../time_scale_utils'; import type { ReferenceBasedIndexPatternColumn } from '../column_types'; +import { isColumnValidAsReference } from '../../layer_helpers'; import { operationDefinitionMap } from '..'; -import type { IndexPatternColumn, RequiredReference } from '..'; export const buildLabelFunction = (ofName: (name?: string) => string) => ( name?: string, @@ -85,23 +85,6 @@ export function checkReferences(layer: IndexPatternLayer, columnId: string) { return errors.length ? errors : undefined; } -export function isColumnValidAsReference({ - column, - validation, -}: { - column: IndexPatternColumn; - validation: RequiredReference; -}): boolean { - if (!column) return false; - const operationType = column.operationType; - const operationDefinition = operationDefinitionMap[operationType]; - return ( - validation.input.includes(operationDefinition.input) && - (!validation.specificOperations || validation.specificOperations.includes(operationType)) && - validation.validateMetadata(column) - ); -} - export function getErrorsForDateReference( layer: IndexPatternLayer, columnId: string, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 9496f95f74dec2..e0d9d864e56562 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -864,7 +864,7 @@ describe('state_helpers', () => { columns: { col1: termsColumn, willBeReference: { - label: 'Count', + label: 'Count of records', dataType: 'number', isBucketed: false, sourceField: 'Records', @@ -878,14 +878,18 @@ describe('state_helpers', () => { }); expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith( - { - indexPatternId: '1', - columnOrder: ['col1', 'willBeReference'], + expect.objectContaining({ columns: { col1: { ...termsColumn, params: { orderBy: { type: 'alphabetical' }, orderDirection: 'asc', size: 5 }, }, + id1: expect.objectContaining({ + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }), willBeReference: expect.objectContaining({ dataType: 'number', isBucketed: false, @@ -893,225 +897,531 @@ describe('state_helpers', () => { }), }, incompleteColumns: {}, - }, + }), 'col1', 'willBeReference' ); }); - it('should not wrap the previous operation when switching to reference', () => { - const layer: IndexPatternLayer = { - indexPatternId: '1', - columnOrder: ['col1'], - columns: { - col1: { - label: 'Count', - customLabel: true, - dataType: 'number' as const, - isBucketed: false, - sourceField: 'Records', - operationType: 'count' as const, - }, - }, - }; - const result = replaceColumn({ - layer, - indexPattern, - columnId: 'col1', - op: 'testReference' as OperationType, + describe('switching from non-reference to reference test cases', () => { + it('should wrap around the previous operation as a reference if possible (case new1)', () => { + const expectedColumn = { + label: 'Count', + customLabel: true, + dataType: 'number' as const, + isBucketed: false, + sourceField: 'Records', + operationType: 'count' as const, + }; + + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { col1: expectedColumn }, + }; + const result = replaceColumn({ + layer, + indexPattern, + columnId: 'col1', + op: 'testReference' as OperationType, + }); + + expect(operationDefinitionMap.testReference.buildColumn).toHaveBeenCalledWith( + expect.objectContaining({ + referenceIds: ['id1'], + }) + ); + expect(result.columnOrder).toEqual(['id1', 'col1']); + expect(result.columns).toEqual( + expect.objectContaining({ + id1: expectedColumn, + col1: expect.any(Object), + }) + ); }); - expect(operationDefinitionMap.testReference.buildColumn).toHaveBeenCalledWith( - expect.objectContaining({ - referenceIds: ['id1'], - }) - ); - expect(result.columns).toEqual( - expect.objectContaining({ - col1: expect.objectContaining({ operationType: 'testReference' }), - }) - ); - }); + it('should create a new no-input operation to use as reference (case new2)', () => { + // @ts-expect-error this function is not valid + operationDefinitionMap.testReference.requiredReferences = [ + { + input: ['none'], + validateMetadata: () => true, + }, + ]; + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Avg', + dataType: 'number' as const, + isBucketed: false, + sourceField: 'bytes', + operationType: 'avg' as const, + }, + }, + }; + const result = replaceColumn({ + layer, + indexPattern, + columnId: 'col1', + // @ts-expect-error + op: 'testReference', + }); - it('should delete the previous references and reset to default values when going from reference to no-input', () => { - // @ts-expect-error this function is not valid - operationDefinitionMap.testReference.requiredReferences = [ - { - input: ['none'], - validateMetadata: () => true, - }, - ]; - const expectedCol = { - dataType: 'string' as const, - isBucketed: true, + expect(result.columnOrder).toEqual(['id1', 'col1']); + expect(result.columns).toEqual({ + id1: expect.objectContaining({ + operationType: 'filters', + }), + col1: expect.objectContaining({ + operationType: 'testReference', + }), + }); + }); - operationType: 'filters' as const, - params: { - // These filters are reset - filters: [{ input: { query: 'field: true', language: 'kuery' }, label: 'Custom label' }], - }, - }; - const layer: IndexPatternLayer = { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: { - ...expectedCol, - label: 'Custom label', - customLabel: true, + it('should use the previous field, but select the best operation, when creating a reference (case new3)', () => { + // @ts-expect-error this function is not valid + operationDefinitionMap.testReference.requiredReferences = [ + { + input: ['field'], + validateMetadata: () => true, + specificOperations: ['cardinality', 'sum', 'avg'], // this order is ignored }, - col2: { - label: 'Test reference', - dataType: 'number', - isBucketed: false, + ]; + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Max', + dataType: 'number' as const, + isBucketed: false, + sourceField: 'bytes', + operationType: 'max' as const, + }, + }, + }; + const result = replaceColumn({ + layer, + indexPattern, + columnId: 'col1', + // @ts-expect-error test only + op: 'testReference', + }); - // @ts-expect-error not a valid type + expect(result.columnOrder).toEqual(['id1', 'col1']); + expect(result.columns).toEqual({ + id1: expect.objectContaining({ + operationType: 'avg', + }), + col1: expect.objectContaining({ operationType: 'testReference', - references: ['col1'], + }), + }); + }); + + it('should ignore previous field and previous operation, but set incomplete operation if known (case new4)', () => { + // @ts-expect-error this function is not valid + operationDefinitionMap.testReference.requiredReferences = [ + { + input: ['field'], + validateMetadata: () => true, + specificOperations: ['cardinality'], }, - }, - }; - expect( - replaceColumn({ + ]; + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Count', + dataType: 'number' as const, + isBucketed: false, + sourceField: 'Records', + operationType: 'count' as const, + }, + }, + }; + const result = replaceColumn({ layer, indexPattern, - columnId: 'col2', - op: 'filters', - }) - ).toEqual( - expect.objectContaining({ - columnOrder: ['col2'], + columnId: 'col1', + // @ts-expect-error + op: 'testReference', + }); + + expect(result.incompleteColumns).toEqual({ + id1: { operationType: 'cardinality' }, + }); + expect(result.columns).toEqual({ + col1: expect.objectContaining({ + operationType: 'testReference', + }), + }); + }); + + it('should leave an empty reference if all the other cases fail (case new6)', () => { + // @ts-expect-error this function is not valid + operationDefinitionMap.testReference.requiredReferences = [ + { + input: ['field'], + validateMetadata: () => false, + specificOperations: [], + }, + ]; + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], columns: { - col2: { - ...expectedCol, - label: 'Filters', - scale: 'ordinal', // added in buildColumn - params: { - filters: [{ input: { query: '', language: 'kuery' }, label: '' }], - }, + col1: { + label: 'Count', + dataType: 'number' as const, + isBucketed: false, + sourceField: 'Records', + operationType: 'count' as const, }, }, - }) - ); + }; + const result = replaceColumn({ + layer, + indexPattern, + columnId: 'col1', + // @ts-expect-error + op: 'testReference', + }); + + expect(result.incompleteColumns).toEqual({}); + expect(result.columns).toEqual({ + col1: expect.objectContaining({ + operationType: 'testReference', + references: ['id1'], + }), + }); + }); }); - it('should delete the inner references when switching away from reference to field-based operation', () => { - const expectedCol = { - label: 'Count of records', - dataType: 'number' as const, - isBucketed: false, + describe('switching from reference to reference test cases', () => { + beforeEach(() => { + operationDefinitionMap.secondTest = { + input: 'fullReference', + displayName: 'Reference test 2', + // @ts-expect-error this type is not statically available + type: 'secondTest', + requiredReferences: [ + { + // Any numeric metric that isn't also a reference + input: ['none', 'field'], + validateMetadata: (meta: OperationMetadata) => + meta.dataType === 'number' && !meta.isBucketed, + }, + ], + // @ts-expect-error don't want to define valid arguments + buildColumn: jest.fn((args) => { + return { + label: 'Test reference', + isBucketed: false, + dataType: 'number', - operationType: 'count' as const, - sourceField: 'Records', - }; - const layer: IndexPatternLayer = { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: expectedCol, - col2: { - label: 'Test reference', - dataType: 'number', - isBucketed: false, + operationType: 'secondTest', + references: args.referenceIds, + }; + }), + isTransferable: jest.fn(), + toExpression: jest.fn().mockReturnValue([]), + getPossibleOperation: jest + .fn() + .mockReturnValue({ dataType: 'number', isBucketed: false }), + getDefaultLabel: jest.fn().mockReturnValue('Test reference'), + }; + }); - // @ts-expect-error not a valid type - operationType: 'testReference', - references: ['col1'], + afterEach(() => { + delete operationDefinitionMap.secondTest; + }); + + it('should use existing references, delete invalid, when switching from one reference to another (case ref1)', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['ref1', 'invalid', 'output'], + columns: { + ref1: { + label: 'Count', + customLabel: true, + dataType: 'number' as const, + isBucketed: false, + + operationType: 'count' as const, + sourceField: 'Records', + }, + invalid: { + label: 'Test reference', + dataType: 'number', + isBucketed: false, + + // @ts-expect-error not a valid type + operationType: 'testReference', + references: [], + }, + output: { + label: 'Test reference', + dataType: 'number', + isBucketed: false, + + // @ts-expect-error not a valid type + operationType: 'testReference', + references: ['ref1', 'invalid'], + }, }, - }, - }; - expect( + }; + expect( + replaceColumn({ + layer, + indexPattern, + columnId: 'output', + // @ts-expect-error not statically available + op: 'secondTest', + }) + ).toEqual( + expect.objectContaining({ + columnOrder: ['ref1', 'output'], + columns: { + ref1: layer.columns.ref1, + output: expect.objectContaining({ references: ['ref1'] }), + }, + incompleteColumns: {}, + }) + ); + }); + + it('should modify a copied object, not the original layer', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['ref1', 'invalid', 'output'], + columns: { + ref1: { + label: 'Count', + customLabel: true, + dataType: 'number' as const, + isBucketed: false, + + operationType: 'count' as const, + sourceField: 'Records', + }, + invalid: { + label: 'Test reference', + dataType: 'number', + isBucketed: false, + + // @ts-expect-error not a valid type + operationType: 'testReference', + references: [], + }, + output: { + label: 'Test reference', + dataType: 'number', + isBucketed: false, + + // @ts-expect-error not a valid type + operationType: 'testReference', + references: ['ref1', 'invalid'], + }, + }, + }; replaceColumn({ layer, indexPattern, - columnId: 'col2', - op: 'count', - field: documentField, - }) - ).toEqual( - expect.objectContaining({ - columnOrder: ['col2'], + columnId: 'output', + // @ts-expect-error not statically available + op: 'secondTest', + }); + expect(layer.columns.output).toEqual( + expect.objectContaining({ references: ['ref1', 'invalid'] }) + ); + }); + + it('should transition by using the field from the previous reference if nothing else works (case new5)', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['fieldReused', 'output'], columns: { - col2: expect.objectContaining(expectedCol), + fieldReused: { + label: 'Date histogram', + dataType: 'date' as const, + isBucketed: true, + operationType: 'date_histogram' as const, + sourceField: 'timestamp', + params: { interval: 'auto' }, + }, + output: { + label: 'Test reference', + dataType: 'number', + isBucketed: false, + // @ts-expect-error not a valid type + operationType: 'testReference', + references: ['fieldReused'], + }, }, - }) - ); + }; + expect( + replaceColumn({ + layer, + indexPattern, + columnId: 'output', + // @ts-expect-error not statically available + op: 'secondTest', + }) + ).toEqual( + expect.objectContaining({ + columnOrder: ['id1', 'output'], + columns: { + id1: expect.objectContaining({ + sourceField: 'timestamp', + operationType: 'cardinality', + }), + output: expect.objectContaining({ references: ['id1'] }), + }, + incompleteColumns: {}, + }) + ); + }); }); - it('should reset when switching from one reference to another', () => { - operationDefinitionMap.secondTest = { - input: 'fullReference', - displayName: 'Reference test 2', - // @ts-expect-error this type is not statically available - type: 'secondTest', - requiredReferences: [ + describe('switching from reference to non-reference', () => { + it('should promote the inner references when switching away from reference to no-input (case a1)', () => { + // @ts-expect-error this function is not valid + operationDefinitionMap.testReference.requiredReferences = [ { - // Any numeric metric that isn't also a reference - input: ['none', 'field'], - validateMetadata: (meta: OperationMetadata) => - meta.dataType === 'number' && !meta.isBucketed, + input: ['none'], + validateMetadata: () => true, }, - ], - // @ts-expect-error don't want to define valid arguments - buildColumn: jest.fn((args) => { - return { - label: 'Test reference', - isBucketed: false, - dataType: 'number', + ]; + const expectedCol = { + label: 'Custom label', + customLabel: true, + dataType: 'string' as const, + isBucketed: true, - operationType: 'secondTest', - references: args.referenceIds, - }; - }), - isTransferable: jest.fn(), - toExpression: jest.fn().mockReturnValue([]), - getPossibleOperation: jest.fn().mockReturnValue({ dataType: 'number', isBucketed: false }), - getDefaultLabel: jest.fn().mockReturnValue('Test reference'), - }; + operationType: 'filters' as const, + params: { + // These filters are reset + filters: [ + { input: { query: 'field: true', language: 'kuery' }, label: 'Custom label' }, + ], + }, + }; + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: expectedCol, + col2: { + label: 'Test reference', + dataType: 'number', + isBucketed: false, - const layer: IndexPatternLayer = { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: { - label: 'Count', - customLabel: true, - dataType: 'number' as const, - isBucketed: false, + // @ts-expect-error not a valid type + operationType: 'testReference', + references: ['col1'], + }, + }, + }; + expect( + replaceColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'filters', + }) + ).toEqual( + expect.objectContaining({ + columnOrder: ['col2'], + columns: { + col2: expectedCol, + }, + }) + ); + }); - operationType: 'count' as const, - sourceField: 'Records', + it('should promote the inner references when switching away from reference to field-based operation (case a2)', () => { + const expectedCol = { + label: 'Count of records', + dataType: 'number' as const, + isBucketed: false, + + operationType: 'count' as const, + sourceField: 'Records', + }; + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: expectedCol, + col2: { + label: 'Default label', + dataType: 'number', + isBucketed: false, + + // @ts-expect-error not a valid type + operationType: 'testReference', + references: ['col1'], + }, }, - col2: { - label: 'Test reference', - dataType: 'number', - isBucketed: false, + }; + expect( + replaceColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'count', + field: documentField, + }) + ).toEqual( + expect.objectContaining({ + columnOrder: ['col2'], + columns: { + col2: expect.objectContaining(expectedCol), + }, + }) + ); + }); - // @ts-expect-error not a valid type - operationType: 'testReference', - references: ['col1'], + it('should promote only the field when going from reference to field-based operation (case a3)', () => { + const expectedColumn = { + dataType: 'number' as const, + isBucketed: false, + sourceField: 'bytes', + operationType: 'avg' as const, + }; + + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['metric', 'ref'], + columns: { + metric: { ...expectedColumn, label: 'Avg', customLabel: true }, + ref: { + label: 'Reference', + dataType: 'number', + isBucketed: false, + operationType: 'derivative', + references: ['metric'], + }, }, - }, - }; - expect( - replaceColumn({ + }; + const result = replaceColumn({ layer, indexPattern, - columnId: 'col2', - // @ts-expect-error not statically available - op: 'secondTest', - }) - ).toEqual( - expect.objectContaining({ - columnOrder: ['col2'], - columns: { - col2: expect.objectContaining({ references: ['id1'] }), - }, - incompleteColumns: {}, - }) - ); + columnId: 'ref', + op: 'sum', + }); - delete operationDefinitionMap.secondTest; + expect(result.columnOrder).toEqual(['ref']); + expect(result.columns).toEqual( + expect.objectContaining({ + ref: expect.objectContaining({ ...expectedColumn, operationType: 'sum' }), + }) + ); + }); }); it('should allow making a replacement on an operation that is being referenced, even if it ends up invalid', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 2d8078b9a61545..21fc36d7418ba6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -5,6 +5,7 @@ */ import _, { partition } from 'lodash'; +import type { OperationMetadata } from '../../types'; import { operationDefinitionMap, operationDefinitions, @@ -59,17 +60,11 @@ export function insertNewColumn({ } const possibleOperation = operationDefinition.getPossibleOperation(); const isBucketed = Boolean(possibleOperation.isBucketed); - if (isBucketed) { - return updateDefaultLabels( - addBucket(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId), - indexPattern - ); - } else { - return updateDefaultLabels( - addMetric(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId), - indexPattern - ); - } + const addOperationFn = isBucketed ? addBucket : addMetric; + return updateDefaultLabels( + addOperationFn(layer, operationDefinition.buildColumn({ ...baseOptions, layer }), columnId), + indexPattern + ); } if (operationDefinition.input === 'fullReference') { @@ -78,9 +73,6 @@ export function insertNewColumn({ } let tempLayer = { ...layer }; const referenceIds = operationDefinition.requiredReferences.map((validation) => { - // TODO: This logic is too simple because it's not using fields. Once we have - // access to the operationSupportMatrix, we should validate the metadata against - // the possible fields const validOperations = Object.values(operationDefinitionMap).filter(({ type }) => isOperationAllowedAsReference({ validation, operationType: type, indexPattern }) ); @@ -240,42 +232,77 @@ export function replaceColumn({ tempLayer = resetIncomplete(tempLayer, columnId); - if (previousDefinition.input === 'fullReference') { - (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => { - tempLayer = deleteColumn({ layer: tempLayer, columnId: id, indexPattern }); + if (operationDefinition.input === 'fullReference') { + return applyReferenceTransition({ + layer: tempLayer, + columnId, + previousColumn, + op, + indexPattern, }); } - tempLayer = resetIncomplete(tempLayer, columnId); + // Makes common inferences about what the user meant when switching away from a reference: + // 1. Switching from "Differences of max" to "max" will promote as-is + // 2. Switching from "Differences of avg of bytes" to "max" will keep the field, but change operation + if ( + previousDefinition.input === 'fullReference' && + (previousColumn as ReferenceBasedIndexPatternColumn).references.length === 1 + ) { + const previousReferenceId = (previousColumn as ReferenceBasedIndexPatternColumn) + .references[0]; + const referenceColumn = layer.columns[previousReferenceId]; + if (referenceColumn) { + const referencedOperation = operationDefinitionMap[referenceColumn.operationType]; + + if (referencedOperation.type === op) { + // Unit tests are labelled as case a1, case a2 + tempLayer = deleteColumn({ + layer: tempLayer, + columnId: previousReferenceId, + indexPattern, + }); - if (operationDefinition.input === 'fullReference') { - const referenceIds = operationDefinition.requiredReferences.map(() => generateId()); + tempLayer = { + ...tempLayer, + columns: { + ...tempLayer.columns, + [columnId]: copyCustomLabel({ ...referenceColumn }, previousColumn), + }, + }; + return updateDefaultLabels( + { + ...tempLayer, + columnOrder: getColumnOrder(tempLayer), + columns: adjustColumnReferencesForChangedColumn(tempLayer, columnId), + }, + indexPattern + ); + } else if ( + !field && + 'sourceField' in referenceColumn && + referencedOperation.input === 'field' && + operationDefinition.input === 'field' + ) { + // Unit test is case a3 + const matchedField = indexPattern.getFieldByName(referenceColumn.sourceField); + if (matchedField && operationDefinition.getPossibleOperationForField(matchedField)) { + field = matchedField; + } + } + } + } - const newLayer = { - ...tempLayer, - columns: { - ...tempLayer.columns, - [columnId]: operationDefinition.buildColumn({ - ...baseOptions, - layer: tempLayer, - referenceIds, - previousColumn, - }), - }, - }; - return updateDefaultLabels( - { - ...tempLayer, - columnOrder: getColumnOrder(newLayer), - columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), - }, - indexPattern - ); + // This logic comes after the transitions because they need to look at previous columns + if (previousDefinition.input === 'fullReference') { + (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => { + tempLayer = deleteColumn({ layer: tempLayer, columnId: id, indexPattern }); + }); } if (operationDefinition.input === 'none') { let newColumn = operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer }); - newColumn = adjustLabel(newColumn, previousColumn); + newColumn = copyCustomLabel(newColumn, previousColumn); const newLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; return updateDefaultLabels( @@ -298,8 +325,18 @@ export function replaceColumn({ }; } + const validOperation = operationDefinition.getPossibleOperationForField(field); + if (!validOperation) { + return { + ...tempLayer, + incompleteColumns: { + ...(tempLayer.incompleteColumns ?? {}), + [columnId]: { operationType: op }, + }, + }; + } let newColumn = operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer, field }); - newColumn = adjustLabel(newColumn, previousColumn); + newColumn = copyCustomLabel(newColumn, previousColumn); const newLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; return updateDefaultLabels( @@ -317,34 +354,274 @@ export function replaceColumn({ previousColumn.sourceField !== field.name ) { // Same operation, new field - const newColumn = operationDefinition.onFieldChange(previousColumn, field); - - if (previousColumn.customLabel) { - newColumn.customLabel = true; - newColumn.label = previousColumn.label; - } + const newColumn = copyCustomLabel( + operationDefinition.onFieldChange(previousColumn, field), + previousColumn + ); - const newLayer = { ...layer, columns: { ...layer.columns, [columnId]: newColumn } }; - return updateDefaultLabels( - { - ...resetIncomplete(layer, columnId), - columnOrder: getColumnOrder(newLayer), - columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), - }, - indexPattern + const newLayer = resetIncomplete( + { ...layer, columns: { ...layer.columns, [columnId]: newColumn } }, + columnId ); + return { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), + }; } else { throw new Error('nothing changed'); } } -function adjustLabel(newColumn: IndexPatternColumn, previousColumn: IndexPatternColumn) { +export function canTransition({ + layer, + columnId, + op, + field, + indexPattern, + filterOperations, +}: ColumnChange & { + filterOperations: (meta: OperationMetadata) => boolean; +}): boolean { + const previousColumn = layer.columns[columnId]; + if (!previousColumn) { + return true; + } + + if (previousColumn.operationType === op) { + return true; + } + + try { + const newLayer = replaceColumn({ layer, columnId, op, field, indexPattern }); + const newDefinition = operationDefinitionMap[op]; + const newColumn = newLayer.columns[columnId]; + return ( + Boolean(newColumn) && + !newLayer.incompleteColumns?.[columnId] && + filterOperations(newColumn) && + !newDefinition.getErrorMessage?.(newLayer, columnId, indexPattern) + ); + } catch (e) { + return false; + } +} + +/** + * Function to transition to a fullReference from any different operation. + * It is always possible to transition to a fullReference, but there are multiple + * passes needed to copy all the previous state. These are the passes in priority + * order, each of which has a unit test: + * + * 1. Case ref1: referenced columns are an exact match + * Side effect: Modifies the reference list directly + * 2. Case new1: the previous column is an exact match. + * Side effect: Deletes and then inserts the previous column + * 3. Case new2: the reference supports `none` inputs, like filters. not visible in the UI. + * Side effect: Inserts a new column + * 4. Case new3, new4: Fuzzy matching on the previous field + * Side effect: Inserts a new column, or an incomplete column + * 5. Fuzzy matching based on the previous references (case new6) + * Side effect: Inserts a new column, or an incomplete column + * Side effect: Modifies the reference list directly + * 6. Case new6: Fall back by generating the column with empty references + */ +function applyReferenceTransition({ + layer, + columnId, + previousColumn, + op, + indexPattern, +}: { + layer: IndexPatternLayer; + columnId: string; + previousColumn: IndexPatternColumn; + op: OperationType; + indexPattern: IndexPattern; +}): IndexPatternLayer { + const operationDefinition = operationDefinitionMap[op]; + + if (operationDefinition.input !== 'fullReference') { + throw new Error(`Requirements for transitioning are not met`); + } + + let hasExactMatch = false; + let hasFieldMatch = false; + + const unusedReferencesQueue = + 'references' in previousColumn + ? [...(previousColumn as ReferenceBasedIndexPatternColumn).references] + : []; + + const referenceIds = operationDefinition.requiredReferences.map((validation) => { + const newId = generateId(); + + // First priority is to use any references that can be kept (case ref1) + if (unusedReferencesQueue.length) { + const otherColumn = layer.columns[unusedReferencesQueue[0]]; + if (isColumnValidAsReference({ validation, column: otherColumn })) { + return unusedReferencesQueue.shift()!; + } + } + + // Second priority is to wrap around the previous column (case new1) + if (!hasExactMatch && isColumnValidAsReference({ validation, column: previousColumn })) { + hasExactMatch = true; + + const newLayer = { ...layer, columns: { ...layer.columns, [newId]: { ...previousColumn } } }; + layer = { + ...layer, + columnOrder: getColumnOrder(newLayer), + columns: adjustColumnReferencesForChangedColumn(newLayer, newId), + }; + return newId; + } + + // Look for any fieldless operations that can be inserted directly (case new2) + if (validation.input.includes('none')) { + const validOperations = operationDefinitions.filter((def) => { + if (def.input !== 'none') return; + return isOperationAllowedAsReference({ + validation, + operationType: def.type, + indexPattern, + }); + }); + + if (validOperations.length === 1) { + layer = insertNewColumn({ + layer, + columnId: newId, + op: validOperations[0].type, + indexPattern, + }); + return newId; + } + } + + // Try to reuse the previous field by finding a possible operation. Because we've alredy + // checked for an exact operation match, this is guaranteed to be different from previousColumn + if (!hasFieldMatch && 'sourceField' in previousColumn && validation.input.includes('field')) { + const defIgnoringfield = operationDefinitions + .filter( + (def) => + def.input === 'field' && + isOperationAllowedAsReference({ validation, operationType: def.type, indexPattern }) + ) + .sort(getSortScoreByPriority); + + // No exact match found, so let's determine that the current field can be reused + const defWithField = defIgnoringfield.filter((def) => { + const previousField = indexPattern.getFieldByName(previousColumn.sourceField); + if (!previousField) return; + return isOperationAllowedAsReference({ + validation, + operationType: def.type, + field: previousField, + indexPattern, + }); + }); + + if (defWithField.length > 0) { + // Found the best match that keeps the field (case new3) + hasFieldMatch = true; + layer = insertNewColumn({ + layer, + columnId: newId, + op: defWithField[0].type, + indexPattern, + field: indexPattern.getFieldByName(previousColumn.sourceField), + }); + return newId; + } else if (defIgnoringfield.length === 1) { + // Can't use the field, but there is an exact match on the operation (case new4) + hasFieldMatch = true; + layer = { + ...layer, + incompleteColumns: { + ...layer.incompleteColumns, + [newId]: { operationType: defIgnoringfield[0].type }, + }, + }; + return newId; + } + } + + // Look for field-based references that we can use to assign a new field-based operation from (case new5) + if (unusedReferencesQueue.length) { + const otherColumn = layer.columns[unusedReferencesQueue[0]]; + if (otherColumn && 'sourceField' in otherColumn && validation.input.includes('field')) { + const previousField = indexPattern.getFieldByName(otherColumn.sourceField); + if (previousField) { + const defWithField = operationDefinitions + .filter( + (def) => + def.input === 'field' && + isOperationAllowedAsReference({ + validation, + operationType: def.type, + field: previousField, + indexPattern, + }) + ) + .sort(getSortScoreByPriority); + + if (defWithField.length > 0) { + layer = insertNewColumn({ + layer, + columnId: newId, + op: defWithField[0].type, + indexPattern, + field: previousField, + }); + return newId; + } + } + } + } + + // The reference is too ambiguous at this point, but instead of throwing an error (case new6) + return newId; + }); + + if (unusedReferencesQueue.length) { + unusedReferencesQueue.forEach((id: string) => { + layer = deleteColumn({ + layer, + columnId: id, + indexPattern, + }); + }); + } + + layer = { + ...layer, + columns: { + ...layer.columns, + [columnId]: operationDefinition.buildColumn({ + indexPattern, + layer, + referenceIds, + previousColumn, + }), + }, + }; + return updateDefaultLabels( + { + ...layer, + columnOrder: getColumnOrder(layer), + columns: adjustColumnReferencesForChangedColumn(layer, columnId), + }, + indexPattern + ); +} + +function copyCustomLabel(newColumn: IndexPatternColumn, previousColumn: IndexPatternColumn) { const adjustedColumn = { ...newColumn }; if (previousColumn.customLabel) { adjustedColumn.customLabel = true; adjustedColumn.label = previousColumn.label; } - return adjustedColumn; } @@ -664,3 +941,20 @@ export function resetIncomplete(layer: IndexPatternLayer, columnId: string): Ind delete incompleteColumns[columnId]; return { ...layer, incompleteColumns }; } + +export function isColumnValidAsReference({ + column, + validation, +}: { + column: IndexPatternColumn; + validation: RequiredReference; +}): boolean { + if (!column) return false; + const operationType = column.operationType; + const operationDefinition = operationDefinitionMap[operationType]; + return ( + validation.input.includes(operationDefinition.input) && + (!validation.specificOperations || validation.specificOperations.includes(operationType)) && + validation.validateMetadata(column) + ); +} diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 6a7448ddc84480..b86d48bfccdabd 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -43,8 +43,13 @@ export const API_ROOT_PATH = `/${GIS_API_PATH}`; export const MVT_GETTILE_API_PATH = 'mvt/getTile'; export const MVT_GETGRIDTILE_API_PATH = 'mvt/getGridTile'; export const MVT_SOURCE_LAYER_NAME = 'source_layer'; +// Identifies vector tile "too many features" feature. +// "too many features" feature is a box showing area that contains too many features for single ES search response export const KBN_TOO_MANY_FEATURES_PROPERTY = '__kbn_too_many_features__'; export const KBN_TOO_MANY_FEATURES_IMAGE_ID = '__kbn_too_many_features_image_id__'; +// Identifies centroid feature. +// Centroids are a single point for representing lines, multiLines, polygons, and multiPolygons +export const KBN_IS_CENTROID_FEATURE = '__kbn_is_centroid_feature__'; const MAP_BASE_URL = `/${MAPS_APP_PATH}/${MAP_PATH}`; export function getNewMapPath() { diff --git a/x-pack/plugins/maps/common/get_centroid_features.test.ts b/x-pack/plugins/maps/common/get_centroid_features.test.ts new file mode 100644 index 00000000000000..e7250203ac3b88 --- /dev/null +++ b/x-pack/plugins/maps/common/get_centroid_features.test.ts @@ -0,0 +1,282 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Feature, FeatureCollection } from 'geojson'; +import { getCentroidFeatures } from './get_centroid_features'; + +test('should not create centroid feature for point and multipoint', () => { + const pointFeature: Feature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [30, 10], + }, + properties: { + prop0: 'value0', + prop1: 0.0, + }, + }; + const multiPointFeature: Feature = { + type: 'Feature', + geometry: { + type: 'MultiPoint', + coordinates: [ + [10, 40], + [40, 30], + [20, 20], + [30, 10], + ], + }, + properties: { + prop0: 'value0', + prop1: 0.0, + }, + }; + const featureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [pointFeature, multiPointFeature], + }; + const centroidFeatures = getCentroidFeatures(featureCollection); + expect(centroidFeatures.length).toBe(0); +}); + +test('should not create centroid for too many features polygon', () => { + const polygonFeature: Feature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [35, 10], + [45, 45], + [15, 40], + [10, 20], + [35, 10], + ], + ], + }, + properties: { + __kbn_too_many_features__: true, + prop0: 'value0', + prop1: 0.0, + }, + }; + const featureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [polygonFeature], + }; + const centroidFeatures = getCentroidFeatures(featureCollection); + expect(centroidFeatures.length).toBe(0); +}); + +test('should create centroid feature for line (even number of points)', () => { + const lineFeature: Feature = { + type: 'Feature', + id: 'myfeature', + geometry: { + type: 'LineString', + coordinates: [ + [102.0, 0.0], + [103.0, 1.0], + [104.0, 0.0], + [105.0, 1.0], + ], + }, + properties: { + prop0: 'value0', + prop1: 0.0, + }, + }; + const featureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [lineFeature], + }; + const centroidFeatures = getCentroidFeatures(featureCollection); + expect(centroidFeatures.length).toBe(1); + expect(centroidFeatures[0]).toEqual({ + type: 'Feature', + id: 'myfeature', + geometry: { + type: 'Point', + coordinates: [103.50003808007737, 0.5000190382261022], + }, + properties: { + __kbn_is_centroid_feature__: true, + prop0: 'value0', + prop1: 0.0, + }, + }); +}); + +test('should create centroid feature for line (odd number of points)', () => { + const lineFeature: Feature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [102.0, 0.0], + [103.0, 1.0], + [104.0, 0.0], + ], + }, + properties: { + prop0: 'value0', + prop1: 0.0, + }, + }; + const featureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [lineFeature], + }; + const centroidFeatures = getCentroidFeatures(featureCollection); + expect(centroidFeatures.length).toBe(1); + expect(centroidFeatures[0]).toEqual({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [103.0, 1.0], + }, + properties: { + __kbn_is_centroid_feature__: true, + prop0: 'value0', + prop1: 0.0, + }, + }); +}); + +test('should create centroid feature for multi line', () => { + const multiLineFeature: Feature = { + type: 'Feature', + geometry: { + type: 'MultiLineString', + coordinates: [ + [ + [10, 10], + [20, 20], + [10, 40], + ], + [ + [40, 40], + [30, 30], + [40, 20], + [30, 10], + ], + ], + }, + properties: { + prop0: 'value0', + prop1: 0.0, + }, + }; + const featureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [multiLineFeature], + }; + const centroidFeatures = getCentroidFeatures(featureCollection); + expect(centroidFeatures.length).toBe(1); + expect(centroidFeatures[0]).toEqual({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [35.56701982106548, 24.717594944805672], + }, + properties: { + __kbn_is_centroid_feature__: true, + prop0: 'value0', + prop1: 0.0, + }, + }); +}); + +test('should create centroid feature for polygon', () => { + const polygonFeature: Feature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [35, 10], + [45, 45], + [15, 40], + [10, 20], + [35, 10], + ], + ], + }, + properties: { + prop0: 'value0', + prop1: 0.0, + }, + }; + const featureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [polygonFeature], + }; + const centroidFeatures = getCentroidFeatures(featureCollection); + expect(centroidFeatures.length).toBe(1); + expect(centroidFeatures[0]).toEqual({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [27.526881720430108, 28.70967741935484], + }, + properties: { + __kbn_is_centroid_feature__: true, + prop0: 'value0', + prop1: 0.0, + }, + }); +}); + +test('should create centroid feature for multi polygon', () => { + const multiPolygonFeature: Feature = { + type: 'Feature', + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [30, 20], + [45, 40], + [10, 40], + [30, 20], + ], + ], + [ + [ + [15, 5], + [40, 10], + [10, 20], + [5, 10], + [15, 5], + ], + ], + ], + }, + properties: { + prop0: 'value0', + prop1: 0.0, + }, + }; + const featureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [multiPolygonFeature], + }; + const centroidFeatures = getCentroidFeatures(featureCollection); + expect(centroidFeatures.length).toBe(1); + expect(centroidFeatures[0]).toEqual({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [28.333333333333332, 33.333333333333336], + }, + properties: { + __kbn_is_centroid_feature__: true, + prop0: 'value0', + prop1: 0.0, + }, + }); +}); diff --git a/x-pack/plugins/maps/common/get_centroid_features.ts b/x-pack/plugins/maps/common/get_centroid_features.ts new file mode 100644 index 00000000000000..9b49b1f7653dcb --- /dev/null +++ b/x-pack/plugins/maps/common/get_centroid_features.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Feature, + FeatureCollection, + Geometry, + LineString, + MultiLineString, + MultiPolygon, +} from 'geojson'; +import turfAlong from '@turf/along'; +import turfArea from '@turf/area'; +// @ts-expect-error +import turfCenterOfMass from '@turf/center-of-mass'; +import turfLength from '@turf/length'; +import { lineString, polygon } from '@turf/helpers'; +import { + GEO_JSON_TYPE, + KBN_IS_CENTROID_FEATURE, + KBN_TOO_MANY_FEATURES_PROPERTY, +} from './constants'; + +export function getCentroidFeatures(featureCollection: FeatureCollection): Feature[] { + const centroidFeatures = []; + for (let i = 0; i < featureCollection.features.length; i++) { + const feature = featureCollection.features[i]; + + // do not add centroid for kibana added features + if (feature.properties?.[KBN_TOO_MANY_FEATURES_PROPERTY]) { + continue; + } + + let centroidGeometry: Geometry | null = null; + if (feature.geometry.type === GEO_JSON_TYPE.LINE_STRING) { + centroidGeometry = getLineCentroid(feature); + } else if (feature.geometry.type === GEO_JSON_TYPE.MULTI_LINE_STRING) { + const coordinates = (feature.geometry as MultiLineString).coordinates; + let longestLine = coordinates[0]; + let longestLength = turfLength(lineString(longestLine)); + for (let j = 1; j < coordinates.length; j++) { + const nextLine = coordinates[j]; + const nextLength = turfLength(lineString(nextLine)); + if (nextLength > longestLength) { + longestLine = nextLine; + longestLength = nextLength; + } + } + centroidGeometry = getLineCentroid(lineString(longestLine) as Feature); + } else if (feature.geometry.type === GEO_JSON_TYPE.POLYGON) { + centroidGeometry = turfCenterOfMass(feature).geometry; + } else if (feature.geometry.type === GEO_JSON_TYPE.MULTI_POLYGON) { + const coordinates = (feature.geometry as MultiPolygon).coordinates; + let largestPolygon = coordinates[0]; + let largestArea = turfArea(polygon(largestPolygon)); + for (let j = 1; j < coordinates.length; j++) { + const nextPolygon = coordinates[j]; + const nextArea = turfArea(polygon(nextPolygon)); + if (nextArea > largestArea) { + largestPolygon = nextPolygon; + largestArea = nextArea; + } + } + centroidGeometry = turfCenterOfMass(polygon(largestPolygon)).geometry; + } + + if (centroidGeometry) { + centroidFeatures.push({ + type: 'Feature', + id: feature.id, + properties: { + ...feature.properties, + [KBN_IS_CENTROID_FEATURE]: true, + }, + geometry: centroidGeometry, + } as Feature); + } + } + return centroidFeatures; +} + +function getLineCentroid(feature: Feature): Geometry { + const length = turfLength(feature); + return turfAlong((feature as unknown) as LineString, length / 2).geometry!; +} diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts index fa82b9dc3b5423..63834d5685e782 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts @@ -86,6 +86,16 @@ function createChoroplethLayerDescriptor({ color: '#3d3d3d', }, }, + [VECTOR_STYLES.LABEL_TEXT]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options, + field: { + name: joinKey, + origin: FIELD_ORIGIN.JOIN, + }, + }, + }, }), }); } diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index 95a452c7ce376e..5f2771ea2ffed0 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -147,6 +147,7 @@ export class TiledVectorLayer extends VectorLayer { this._setMbPointsProperties(mbMap, sourceMeta.layerName); this._setMbLinePolygonProperties(mbMap, sourceMeta.layerName); + this._setMbCentroidProperties(mbMap, sourceMeta.layerName); } _requiresPrevSourceCleanup(mbMap: MbMap): boolean { diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index add5a980258f38..f72d2c0e6ead98 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -12,6 +12,7 @@ import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AbstractLayer } from '../layer'; import { IVectorStyle, VectorStyle } from '../../styles/vector/vector_style'; +import { getCentroidFeatures } from '../../../../common/get_centroid_features'; import { FEATURE_ID_PROPERTY_NAME, SOURCE_DATA_REQUEST_ID, @@ -26,6 +27,7 @@ import { LAYER_STYLE_TYPE, KBN_TOO_MANY_FEATURES_IMAGE_ID, FieldFormatter, + VECTOR_SHAPE_TYPE, } from '../../../../common/constants'; import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; import { DataRequestAbortError } from '../../util/data_request'; @@ -37,6 +39,7 @@ import { import { assignFeatureIds } from '../../util/assign_feature_ids'; import { getFeatureCollectionBounds } from '../../util/get_feature_collection_bounds'; import { + getCentroidFilterExpression, getFillFilterExpression, getLineFilterExpression, getPointFilterExpression, @@ -519,6 +522,13 @@ export class VectorLayer extends AbstractLayer { } ); const layerFeatureCollection = assignFeatureIds(sourceFeatureCollection); + const supportedShapes = await source.getSupportedShapeTypes(); + if ( + supportedShapes.includes(VECTOR_SHAPE_TYPE.LINE) || + supportedShapes.includes(VECTOR_SHAPE_TYPE.POLYGON) + ) { + layerFeatureCollection.features.push(...getCentroidFeatures(layerFeatureCollection)); + } stopLoading(dataRequestId, requestToken, layerFeatureCollection, meta); return { refreshed: true, @@ -995,9 +1005,41 @@ export class VectorLayer extends AbstractLayer { mbMap.setLayerZoomRange(tooManyFeaturesLayerId, this.getMinZoom(), this.getMaxZoom()); } + _setMbCentroidProperties(mbMap: MbMap, mvtSourceLayer?: string) { + const centroidLayerId = this._getMbCentroidLayerId(); + const centroidLayer = mbMap.getLayer(centroidLayerId); + if (!centroidLayer) { + const mbLayer: MbLayer = { + id: centroidLayerId, + type: 'symbol', + source: this.getId(), + }; + if (mvtSourceLayer) { + mbLayer['source-layer'] = mvtSourceLayer; + } + mbMap.addLayer(mbLayer); + } + + const filterExpr = getCentroidFilterExpression(this.hasJoins()); + if (filterExpr !== mbMap.getFilter(centroidLayerId)) { + mbMap.setFilter(centroidLayerId, filterExpr); + } + + this.getCurrentStyle().setMBPropertiesForLabelText({ + alpha: this.getAlpha(), + mbMap, + textLayerId: centroidLayerId, + }); + + this.syncVisibilityWithMb(mbMap, centroidLayerId); + mbMap.setLayerZoomRange(centroidLayerId, this.getMinZoom(), this.getMaxZoom()); + } + _syncStylePropertiesWithMb(mbMap: MbMap) { this._setMbPointsProperties(mbMap); this._setMbLinePolygonProperties(mbMap); + // centroid layers added after polygon layers to ensure they are on top of polygon layers + this._setMbCentroidProperties(mbMap); } _syncSourceBindingWithMb(mbMap: MbMap) { @@ -1037,6 +1079,10 @@ export class VectorLayer extends AbstractLayer { return this.makeMbLayerId('text'); } + _getMbCentroidLayerId() { + return this.makeMbLayerId('centroid'); + } + _getMbSymbolLayerId() { return this.makeMbLayerId('symbol'); } @@ -1057,6 +1103,7 @@ export class VectorLayer extends AbstractLayer { return [ this._getMbPointLayerId(), this._getMbTextLayerId(), + this._getMbCentroidLayerId(), this._getMbSymbolLayerId(), this._getMbLineLayerId(), this._getMbPolygonLayerId(), diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap index 312f8e5d91ffad..be8c9b0750b94e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/__snapshots__/vector_style_editor.test.tsx.snap @@ -151,6 +151,221 @@ exports[`should render 1`] = ` } } /> + + + + + + + + + + + + + + + + + + + + + + { {this._renderLineWidth()} + + + {this._renderLabelProperties()} ); } @@ -481,6 +484,9 @@ export class VectorStyleEditor extends Component { {this._renderLineWidth()} + + + {this._renderLabelProperties()} ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js index 94090c8abfe4f8..acbf2cc8e72ba6 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js @@ -221,6 +221,14 @@ describe('pluckStyleMetaFromSourceDataRequest', () => { }, properties: {}, }, + { + geometry: { + type: 'Point', + }, + properties: { + __kbn_is_centroid_feature__: true, + }, + }, ], }, }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index 1c36961aae1b1f..9bf4cafd66407d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -14,6 +14,7 @@ import { DEFAULT_ICON, FIELD_ORIGIN, GEO_JSON_TYPE, + KBN_IS_CENTROID_FEATURE, LAYER_STYLE_TYPE, SOURCE_FORMATTERS_DATA_REQUEST_ID, STYLE_TYPE, @@ -493,6 +494,12 @@ export class VectorStyle implements IVectorStyle { if (supportedFeatures.length > 1) { for (let i = 0; i < features.length; i++) { const feature = features[i]; + + // ignore centroid features as they are added for styling and not part of the real data set + if (feature.properties[KBN_IS_CENTROID_FEATURE]) { + continue; + } + if (!hasFeatureType[VECTOR_SHAPE_TYPE.POINT] && POINTS.includes(feature.geometry.type)) { hasFeatureType[VECTOR_SHAPE_TYPE.POINT] = true; } diff --git a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts index 0da6f632eb4a84..5b82305cd84a1d 100644 --- a/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts +++ b/x-pack/plugins/maps/public/classes/util/mb_filter_expressions.ts @@ -7,16 +7,19 @@ import { GEO_JSON_TYPE, FEATURE_VISIBLE_PROPERTY_NAME, + KBN_IS_CENTROID_FEATURE, KBN_TOO_MANY_FEATURES_PROPERTY, } from '../../../common/constants'; export const EXCLUDE_TOO_MANY_FEATURES_BOX = ['!=', ['get', KBN_TOO_MANY_FEATURES_PROPERTY], true]; +const EXCLUDE_CENTROID_FEATURES = ['!=', ['get', KBN_IS_CENTROID_FEATURE], true]; const VISIBILITY_FILTER_CLAUSE = ['all', ['==', ['get', FEATURE_VISIBLE_PROPERTY_NAME], true]]; -const TOO_MANY_FEATURES_FILTER = ['all', EXCLUDE_TOO_MANY_FEATURES_BOX]; +// Kibana features are features added by kibana that do not exist in real data +const EXCLUDE_KBN_FEATURES = ['all', EXCLUDE_TOO_MANY_FEATURES_BOX, EXCLUDE_CENTROID_FEATURES]; const CLOSED_SHAPE_MB_FILTER = [ - ...TOO_MANY_FEATURES_FILTER, + ...EXCLUDE_KBN_FEATURES, [ 'any', ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], @@ -27,7 +30,7 @@ const CLOSED_SHAPE_MB_FILTER = [ const VISIBLE_CLOSED_SHAPE_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, CLOSED_SHAPE_MB_FILTER]; const ALL_SHAPE_MB_FILTER = [ - ...TOO_MANY_FEATURES_FILTER, + ...EXCLUDE_KBN_FEATURES, [ 'any', ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON], @@ -40,7 +43,7 @@ const ALL_SHAPE_MB_FILTER = [ const VISIBLE_ALL_SHAPE_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, ALL_SHAPE_MB_FILTER]; const POINT_MB_FILTER = [ - ...TOO_MANY_FEATURES_FILTER, + ...EXCLUDE_KBN_FEATURES, [ 'any', ['==', ['geometry-type'], GEO_JSON_TYPE.POINT], @@ -50,6 +53,10 @@ const POINT_MB_FILTER = [ const VISIBLE_POINT_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, POINT_MB_FILTER]; +const CENTROID_MB_FILTER = ['all', ['==', ['get', KBN_IS_CENTROID_FEATURE], true]]; + +const VISIBLE_CENTROID_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE, CENTROID_MB_FILTER]; + export function getFillFilterExpression(hasJoins: boolean): unknown[] { return hasJoins ? VISIBLE_CLOSED_SHAPE_MB_FILTER : CLOSED_SHAPE_MB_FILTER; } @@ -61,3 +68,7 @@ export function getLineFilterExpression(hasJoins: boolean): unknown[] { export function getPointFilterExpression(hasJoins: boolean): unknown[] { return hasJoins ? VISIBLE_POINT_MB_FILTER : POINT_MB_FILTER; } + +export function getCentroidFilterExpression(hasJoins: boolean): unknown[] { + return hasJoins ? VISIBLE_CENTROID_MB_FILTER : CENTROID_MB_FILTER; +} diff --git a/x-pack/plugins/maps/server/mvt/get_tile.test.ts b/x-pack/plugins/maps/server/mvt/get_tile.test.ts index 3660039f2513c0..634b898fdc18c9 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.test.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.test.ts @@ -7,7 +7,12 @@ import { getGridTile, getTile } from './get_tile'; import { TILE_GRIDAGGS, TILE_SEARCHES } from './__tests__/tile_es_responses'; import { Logger } from 'src/core/server'; -import { ES_GEO_FIELD_TYPE, MVT_SOURCE_LAYER_NAME, RENDER_AS } from '../../common/constants'; +import { + ES_GEO_FIELD_TYPE, + KBN_IS_CENTROID_FEATURE, + MVT_SOURCE_LAYER_NAME, + RENDER_AS, +} from '../../common/constants'; // @ts-expect-error import { VectorTile, VectorTileLayer } from '@mapbox/vector-tile'; @@ -18,7 +23,6 @@ interface ITileLayerJsonExpectation { version: number; name: string; extent: number; - length: number; features: Array<{ id: string | number | undefined; type: number; @@ -75,7 +79,6 @@ describe('getTile', () => { version: 2, name: 'source_layer', extent: 4096, - length: 1, features: [ { id: undefined, @@ -97,6 +100,18 @@ describe('getTile', () => { ], ], }, + { + id: undefined, + type: 1, + properties: { + __kbn__feature_id__: 'poly:G7PRMXQBgyyZ-h5iYibj:0', + _id: 'G7PRMXQBgyyZ-h5iYibj', + _index: 'poly', + [KBN_IS_CENTROID_FEATURE]: true, + }, + extent: 4096, + pointArrays: [[{ x: 1470, y: 1702 }]], + }, ], }); }); @@ -166,7 +181,6 @@ describe('getGridTile', () => { version: 2, name: 'source_layer', extent: 4096, - length: 1, features: [ { id: undefined, @@ -189,7 +203,6 @@ describe('getGridTile', () => { version: 2, name: 'source_layer', extent: 4096, - length: 1, features: [ { id: undefined, @@ -209,6 +222,17 @@ describe('getGridTile', () => { ], ], }, + { + id: undefined, + type: 1, + properties: { + ['avg_of_TOTAL_AV']: 5398920.390458991, + doc_count: 42637, + [KBN_IS_CENTROID_FEATURE]: true, + }, + extent: 4096, + pointArrays: [[{ x: 1200, y: 1552 }]], + }, ], }); }); diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index cc87f3b65522eb..ee458490427150 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -24,6 +24,7 @@ import { import { convertRegularRespToGeoJson, hitsToGeoJson } from '../../common/elasticsearch_util'; import { flattenHit } from './util'; import { ESBounds, tile2lat, tile2long, tileToESBbox } from '../../common/geo_tile_utils'; +import { getCentroidFeatures } from '../../common/get_centroid_features'; export async function getGridTile({ logger, @@ -270,6 +271,7 @@ function createMvtTile( x: number, y: number ): Buffer | null { + featureCollection.features.push(...getCentroidFeatures(featureCollection)); const tileIndex = geojsonvt(featureCollection, { maxZoom: 24, // max zoom to preserve detail on; can't be higher than 24 tolerance: 3, // simplification tolerance (higher means simpler) diff --git a/x-pack/plugins/runtime_fields/README.md b/x-pack/plugins/runtime_field_editor/README.md similarity index 98% rename from x-pack/plugins/runtime_fields/README.md rename to x-pack/plugins/runtime_field_editor/README.md index eb7b31e6e1154e..1eddfd75a39f3e 100644 --- a/x-pack/plugins/runtime_fields/README.md +++ b/x-pack/plugins/runtime_field_editor/README.md @@ -1,6 +1,6 @@ -# Runtime fields +# Runtime fields editor -Welcome to the home of the runtime field editor and everything related to runtime fields! +Welcome to the home of the runtime field editor! ## The runtime field editor diff --git a/x-pack/plugins/runtime_fields/jest.config.js b/x-pack/plugins/runtime_field_editor/jest.config.js similarity index 83% rename from x-pack/plugins/runtime_fields/jest.config.js rename to x-pack/plugins/runtime_field_editor/jest.config.js index 9c4ec56593c8b3..f5a9e988fd867d 100644 --- a/x-pack/plugins/runtime_fields/jest.config.js +++ b/x-pack/plugins/runtime_field_editor/jest.config.js @@ -7,5 +7,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', - roots: ['/x-pack/plugins/runtime_fields'], + roots: ['/x-pack/plugins/runtime_field_editor'], }; diff --git a/x-pack/plugins/runtime_fields/kibana.json b/x-pack/plugins/runtime_field_editor/kibana.json similarity index 88% rename from x-pack/plugins/runtime_fields/kibana.json rename to x-pack/plugins/runtime_field_editor/kibana.json index 65932c723c4742..3270ada544ba4e 100644 --- a/x-pack/plugins/runtime_fields/kibana.json +++ b/x-pack/plugins/runtime_field_editor/kibana.json @@ -1,5 +1,5 @@ { - "id": "runtimeFields", + "id": "runtimeFieldEditor", "version": "kibana", "server": false, "ui": true, diff --git a/x-pack/plugins/runtime_fields/public/__jest__/setup_environment.tsx b/x-pack/plugins/runtime_field_editor/public/__jest__/setup_environment.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/__jest__/setup_environment.tsx rename to x-pack/plugins/runtime_field_editor/public/__jest__/setup_environment.tsx diff --git a/x-pack/plugins/runtime_fields/public/components/index.ts b/x-pack/plugins/runtime_field_editor/public/components/index.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/index.ts rename to x-pack/plugins/runtime_field_editor/public/components/index.ts diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/index.ts b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor/index.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_editor/index.ts rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor/index.ts diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor/runtime_field_editor.test.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor/runtime_field_editor.test.tsx diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.tsx b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor/runtime_field_editor.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.tsx rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor/runtime_field_editor.tsx diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/index.ts b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor_flyout_content/index.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/index.ts rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor_flyout_content/index.ts diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.test.tsx b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.test.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.test.tsx rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.test.tsx diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.tsx b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.tsx rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_editor_flyout_content/runtime_field_editor_flyout_content.tsx diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/index.ts b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/index.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_form/index.ts rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/index.ts diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.test.tsx b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/runtime_field_form.test.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.test.tsx rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/runtime_field_form.test.tsx diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/runtime_field_form.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/runtime_field_form.tsx diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/schema.ts b/x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/schema.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/components/runtime_field_form/schema.ts rename to x-pack/plugins/runtime_field_editor/public/components/runtime_field_form/schema.ts diff --git a/x-pack/plugins/runtime_fields/public/constants.ts b/x-pack/plugins/runtime_field_editor/public/constants.ts similarity index 75% rename from x-pack/plugins/runtime_fields/public/constants.ts rename to x-pack/plugins/runtime_field_editor/public/constants.ts index 017b58c246afea..eebc3007d79d5c 100644 --- a/x-pack/plugins/runtime_fields/public/constants.ts +++ b/x-pack/plugins/runtime_field_editor/public/constants.ts @@ -3,11 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ComboBoxOption } from './types'; - -export const RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const; - -type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; +import { ComboBoxOption, RuntimeType } from './types'; export const RUNTIME_FIELD_OPTIONS: Array> = [ { diff --git a/x-pack/plugins/runtime_fields/public/index.ts b/x-pack/plugins/runtime_field_editor/public/index.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/index.ts rename to x-pack/plugins/runtime_field_editor/public/index.ts diff --git a/x-pack/plugins/runtime_fields/public/lib/documentation.ts b/x-pack/plugins/runtime_field_editor/public/lib/documentation.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/lib/documentation.ts rename to x-pack/plugins/runtime_field_editor/public/lib/documentation.ts diff --git a/x-pack/plugins/runtime_fields/public/lib/index.ts b/x-pack/plugins/runtime_field_editor/public/lib/index.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/lib/index.ts rename to x-pack/plugins/runtime_field_editor/public/lib/index.ts diff --git a/x-pack/plugins/runtime_fields/public/load_editor.tsx b/x-pack/plugins/runtime_field_editor/public/load_editor.tsx similarity index 100% rename from x-pack/plugins/runtime_fields/public/load_editor.tsx rename to x-pack/plugins/runtime_field_editor/public/load_editor.tsx diff --git a/x-pack/plugins/runtime_fields/public/plugin.test.ts b/x-pack/plugins/runtime_field_editor/public/plugin.test.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/plugin.test.ts rename to x-pack/plugins/runtime_field_editor/public/plugin.test.ts diff --git a/x-pack/plugins/runtime_fields/public/plugin.ts b/x-pack/plugins/runtime_field_editor/public/plugin.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/plugin.ts rename to x-pack/plugins/runtime_field_editor/public/plugin.ts diff --git a/x-pack/plugins/runtime_fields/public/shared_imports.ts b/x-pack/plugins/runtime_field_editor/public/shared_imports.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/shared_imports.ts rename to x-pack/plugins/runtime_field_editor/public/shared_imports.ts diff --git a/x-pack/plugins/runtime_fields/public/test_utils.ts b/x-pack/plugins/runtime_field_editor/public/test_utils.ts similarity index 100% rename from x-pack/plugins/runtime_fields/public/test_utils.ts rename to x-pack/plugins/runtime_field_editor/public/test_utils.ts diff --git a/x-pack/plugins/runtime_fields/public/types.ts b/x-pack/plugins/runtime_field_editor/public/types.ts similarity index 80% rename from x-pack/plugins/runtime_fields/public/types.ts rename to x-pack/plugins/runtime_field_editor/public/types.ts index b1bbb06d796551..984d6ab9f86558 100644 --- a/x-pack/plugins/runtime_fields/public/types.ts +++ b/x-pack/plugins/runtime_field_editor/public/types.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { DataPublicPluginStart } from 'src/plugins/data/public'; +export type { + RuntimeField, + RuntimeType, + RUNTIME_FIELD_TYPES, +} from 'src/plugins/runtime_fields/common'; -import { RUNTIME_FIELD_TYPES } from './constants'; import { OpenRuntimeFieldEditorProps } from './load_editor'; export interface LoadEditorResponse { @@ -26,16 +30,6 @@ export interface StartPlugins { data: DataPublicPluginStart; } -export type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; - -export interface RuntimeField { - name: string; - type: RuntimeType; - script: { - source: string; - }; -} - export interface ComboBoxOption { label: string; value?: T; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx index f91efcb3b19b07..022fd5cdb04ea7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx @@ -34,7 +34,7 @@ export const getAllExceptionListsColumns = ( width: '15%', render: (value: ExceptionListInfo['list_id']) => ( - <>{value} +

{value}

), }, @@ -120,6 +120,8 @@ export const getAllExceptionListsColumns = ( onClick={onDelete({ id, listId, namespaceType })} aria-label="Delete exception list" iconType="trash" + isDisabled={listId === 'endpoint_list'} + data-test-subj="exceptionsTableDeleteButton" /> ), }, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx new file mode 100644 index 00000000000000..d5f3af7cf50318 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { TestProviders } from '../../../../../../common/mock'; +import { mockHistory } from '../../../../../../common/utils/route/index.test'; +import { getExceptionListSchemaMock } from '../../../../../../../../lists/common/schemas/response/exception_list_schema.mock'; + +import { ExceptionListsTable } from './exceptions_table'; +import { useKibana } from '../../../../../../common/lib/kibana'; +import { useApi, useExceptionLists } from '../../../../../../shared_imports'; +import { useAllExceptionLists } from './use_all_exception_lists'; + +jest.mock('../../../../../../common/lib/kibana'); +jest.mock('./use_all_exception_lists'); +jest.mock('../../../../../../shared_imports'); + +describe('ExceptionListsTable', () => { + const exceptionList1 = getExceptionListSchemaMock(); + const exceptionList2 = { ...getExceptionListSchemaMock(), list_id: 'not_endpoint_list', id: '2' }; + + beforeEach(() => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + http: {}, + notifications: { + toasts: { + addError: jest.fn(), + }, + }, + }, + }); + + (useApi as jest.Mock).mockReturnValue({ + deleteExceptionList: jest.fn(), + exportExceptionList: jest.fn(), + }); + + (useExceptionLists as jest.Mock).mockReturnValue([ + false, + [exceptionList1, exceptionList2], + { + page: 1, + perPage: 20, + total: 2, + }, + jest.fn(), + ]); + + (useAllExceptionLists as jest.Mock).mockReturnValue([ + false, + [ + { ...exceptionList1, rules: [] }, + { ...exceptionList2, rules: [] }, + ], + { + not_endpoint_list: exceptionList2, + endpoint_list: exceptionList1, + }, + ]); + }); + + it('renders delete option disabled if list is "endpoint_list"', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="exceptionsTableListId"]').at(0).text()).toEqual( + 'endpoint_list' + ); + expect( + wrapper.find('[data-test-subj="exceptionsTableDeleteButton"] button').at(0).prop('disabled') + ).toBeTruthy(); + + expect(wrapper.find('[data-test-subj="exceptionsTableListId"]').at(1).text()).toEqual( + 'not_endpoint_list' + ); + expect( + wrapper.find('[data-test-subj="exceptionsTableDeleteButton"] button').at(1).prop('disabled') + ).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/task_manager/tsconfig.json b/x-pack/plugins/task_manager/tsconfig.json new file mode 100644 index 00000000000000..a72b678da1f7c1 --- /dev/null +++ b/x-pack/plugins/task_manager/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "server/**/*.json", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx index 5fc28d5c629d4f..2c74f3391f9c12 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx @@ -17,9 +17,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ReducerAction } from './connector_reducer'; import { - ActionConnector, IErrorObject, ActionTypeRegistryContract, UserConfiguredActionConnector, @@ -28,8 +26,11 @@ import { import { hasSaveActionsCapability } from '../../lib/capabilities'; import { useKibana } from '../../../common/lib/kibana'; import { SectionLoading } from '../../components/section_loading'; +import { ConnectorReducerAction } from './connector_reducer'; -export function validateBaseProperties(actionObject: ActionConnector) { +export function validateBaseProperties( + actionObject: UserConfiguredActionConnector +) { const validationResult = { errors: {} }; const verrors = { name: new Array(), @@ -78,14 +79,14 @@ interface ActionConnectorProps< ConnectorSecrets = Record > { connector: UserConfiguredActionConnector; - dispatch: React.Dispatch; - actionTypeName: string; - serverError?: { - body: { message: string; error: string }; - }; + dispatch: React.Dispatch>; errors: IErrorObject; actionTypeRegistry: ActionTypeRegistryContract; consumer?: string; + actionTypeName?: string; + serverError?: { + body: { message: string; error: string }; + }; } export const ActionConnectorForm = ({ @@ -103,15 +104,31 @@ export const ActionConnectorForm = ({ } = useKibana().services; const canSave = hasSaveActionsCapability(capabilities); - const setActionProperty = (key: string, value: any) => { + const setActionProperty = < + Key extends keyof UserConfiguredActionConnector< + Record, + Record + > + >( + key: Key, + value: + | UserConfiguredActionConnector, Record>[Key] + | null + ) => { dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); }; - const setActionConfigProperty = (key: string, value: any) => { + const setActionConfigProperty = >( + key: Key, + value: Record[Key] + ) => { dispatch({ command: { type: 'setConfigProperty' }, payload: { key, value } }); }; - const setActionSecretsProperty = (key: string, value: any) => { + const setActionSecretsProperty = >( + key: Key, + value: Record[Key] + ) => { dispatch({ command: { type: 'setSecretsProperty' }, payload: { key, value } }); }; @@ -135,7 +152,7 @@ export const ActionConnectorForm = ({ id="xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionConfigurationWarningDescriptionText" defaultMessage="To create this connector, you must configure at least one {actionType} account. {docLink}" values={{ - actionType: actionTypeName, + actionType: actionTypeName ?? connector.actionTypeId, docLink: ( = ({ const [hasActionsUpgradeableByTrial, setHasActionsUpgradeableByTrial] = useState(false); // hooks - const initialConnector = { + const initialConnector: InitialConnector, Record> = { actionTypeId: actionType?.id ?? '', config: {}, secrets: {}, - } as ActionConnector; - const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector }); - const setActionProperty = (key: string, value: any) => { + }; + + const reducer: ConnectorReducer< + Record, + Record + > = createConnectorReducer, Record>(); + const [{ connector }, dispatch] = useReducer(reducer, { + connector: initialConnector as UserConfiguredActionConnector< + Record, + Record + >, + }); + + const setActionProperty = ( + key: Key, + value: + | UserConfiguredActionConnector, Record>[Key] + | null + ) => { dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); }; + const setConnector = (value: any) => { dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } }); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 205066c4ecace5..d48c649c8d41ad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -18,11 +18,16 @@ import { EuiButtonEmpty } from '@elastic/eui'; import { EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ActionConnectorForm, getConnectorErrors } from './action_connector_form'; -import { connectorReducer } from './connector_reducer'; +import { createConnectorReducer, InitialConnector, ConnectorReducer } from './connector_reducer'; import { createActionConnector } from '../../lib/action_connector_api'; import './connector_add_modal.scss'; import { hasSaveActionsCapability } from '../../lib/capabilities'; -import { ActionType, ActionConnector, ActionTypeRegistryContract } from '../../../types'; +import { + ActionType, + ActionConnector, + ActionTypeRegistryContract, + UserConfiguredActionConnector, +} from '../../../types'; import { useKibana } from '../../../common/lib/kibana'; import { getConnectorWithInvalidatedFields } from '../../lib/value_validators'; @@ -47,7 +52,10 @@ export const ConnectorAddModal = ({ application: { capabilities }, } = useKibana().services; let hasErrors = false; - const initialConnector = useMemo( + const initialConnector: InitialConnector< + Record, + Record + > = useMemo( () => ({ actionTypeId: actionType.id, config: {}, @@ -58,7 +66,16 @@ export const ConnectorAddModal = ({ const [isSaving, setIsSaving] = useState(false); const canSave = hasSaveActionsCapability(capabilities); - const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector }); + const reducer: ConnectorReducer< + Record, + Record + > = createConnectorReducer, Record>(); + const [{ connector }, dispatch] = useReducer(reducer, { + connector: initialConnector as UserConfiguredActionConnector< + Record, + Record + >, + }); const setConnector = (value: any) => { dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } }); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index 75a8e9bbe82700..f4fec873ecc940 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -26,8 +26,12 @@ import { i18n } from '@kbn/i18n'; import { Option, none, some } from 'fp-ts/lib/Option'; import { ActionConnectorForm, getConnectorErrors } from './action_connector_form'; import { TestConnectorForm } from './test_connector_form'; -import { ActionConnector, ActionTypeRegistryContract } from '../../../types'; -import { connectorReducer } from './connector_reducer'; +import { + ActionConnector, + ActionTypeRegistryContract, + UserConfiguredActionConnector, +} from '../../../types'; +import { ConnectorReducer, createConnectorReducer } from './connector_reducer'; import { updateActionConnector, executeAction } from '../../lib/action_connector_api'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { @@ -66,17 +70,29 @@ export const ConnectorEditFlyout = ({ docLinks, application: { capabilities }, } = useKibana().services; + const getConnectorWithoutSecrets = () => ({ + ...(initialConnector as UserConfiguredActionConnector< + Record, + Record + >), + secrets: {}, + }); const canSave = hasSaveActionsCapability(capabilities); - const [{ connector }, dispatch] = useReducer(connectorReducer, { - connector: { ...initialConnector, secrets: {} }, + const reducer: ConnectorReducer< + Record, + Record + > = createConnectorReducer, Record>(); + const [{ connector }, dispatch] = useReducer(reducer, { + connector: getConnectorWithoutSecrets(), }); const [isSaving, setIsSaving] = useState(false); const [selectedTab, setTab] = useState(tab); const [hasChanges, setHasChanges] = useState(false); - const setConnector = (key: string, value: any) => { - dispatch({ command: { type: 'setConnector' }, payload: { key, value } }); + + const setConnector = (value: any) => { + dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } }); }; const [testExecutionActionParams, setTestExecutionActionParams] = useState< @@ -101,7 +117,7 @@ export const ConnectorEditFlyout = ({ ); const closeFlyout = useCallback(() => { - setConnector('connector', { ...initialConnector, secrets: {} }); + setConnector(getConnectorWithoutSecrets()); setHasChanges(false); setTestExecutionResult(none); onClose(); @@ -220,7 +236,6 @@ export const ConnectorEditFlyout = ({ const onSaveClicked = async (closeAfterSave: boolean = true) => { if (hasErrors) { setConnector( - 'connector', getConnectorWithInvalidatedFields( connector, configErrors, @@ -282,7 +297,6 @@ export const ConnectorEditFlyout = ({ { setHasChanges(true); // if the user changes the connector, "forget" the last execution diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts index e469a501089128..b95203deaa129f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.test.ts @@ -3,11 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { connectorReducer } from './connector_reducer'; -import { ActionConnector } from '../../../types'; +import { UserConfiguredActionConnector } from '../../../types'; +import { createConnectorReducer, ConnectorReducer } from './connector_reducer'; describe('connector reducer', () => { - let initialConnector: ActionConnector; + let initialConnector: UserConfiguredActionConnector< + Record, + Record + >; beforeAll(() => { initialConnector = { secrets: {}, @@ -20,6 +23,11 @@ describe('connector reducer', () => { }; }); + const connectorReducer: ConnectorReducer< + Record, + Record + > = createConnectorReducer, Record>(); + test('if property name was changed', () => { const updatedConnector = connectorReducer( { connector: initialConnector }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts index 4d094cd869420a..a82a2041b4903f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts @@ -4,30 +4,69 @@ * you may not use this file except in compliance with the Elastic License. */ import { isEqual } from 'lodash'; +import { Reducer } from 'react'; +import { UserConfiguredActionConnector } from '../../../types'; -interface CommandType { - type: 'setConnector' | 'setProperty' | 'setConfigProperty' | 'setSecretsProperty'; +export type InitialConnector = Partial< + UserConfiguredActionConnector +> & + Pick, 'actionTypeId' | 'config' | 'secrets'>; + +interface CommandType< + T extends 'setConnector' | 'setProperty' | 'setConfigProperty' | 'setSecretsProperty' +> { + type: T; } -export interface ActionState { - connector: any; +interface Payload { + key: Keys; + value: Value; } -export interface ReducerAction { - command: CommandType; - payload: { - key: string; - value: any; - }; +interface TPayload { + key: Key; + value: T[Key] | null; } -export const connectorReducer = (state: ActionState, action: ReducerAction) => { - const { command, payload } = action; +export type ConnectorReducerAction = + | { + command: CommandType<'setConnector'>; + payload: Payload<'connector', InitialConnector>; + } + | { + command: CommandType<'setProperty'>; + payload: TPayload< + UserConfiguredActionConnector, + keyof UserConfiguredActionConnector + >; + } + | { + command: CommandType<'setConfigProperty'>; + payload: TPayload; + } + | { + command: CommandType<'setSecretsProperty'>; + payload: TPayload; + }; + +export type ConnectorReducer = Reducer< + { connector: UserConfiguredActionConnector }, + ConnectorReducerAction +>; + +export const createConnectorReducer = () => < + ConnectorPhase extends + | InitialConnector + | UserConfiguredActionConnector +>( + state: { connector: ConnectorPhase }, + action: ConnectorReducerAction +) => { const { connector } = state; - switch (command.type) { + switch (action.command.type) { case 'setConnector': { - const { key, value } = payload; + const { key, value } = action.payload as Payload<'connector', ConnectorPhase>; if (key === 'connector') { return { ...state, @@ -38,7 +77,10 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => { } } case 'setProperty': { - const { key, value } = payload; + const { key, value } = action.payload as TPayload< + UserConfiguredActionConnector, + keyof UserConfiguredActionConnector + >; if (isEqual(connector[key], value)) { return state; } else { @@ -52,7 +94,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => { } } case 'setConfigProperty': { - const { key, value } = payload; + const { key, value } = action.payload as TPayload; if (isEqual(connector.config[key], value)) { return state; } else { @@ -61,7 +103,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => { connector: { ...connector, config: { - ...connector.config, + ...(connector.config as Config), [key]: value, }, }, @@ -69,7 +111,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => { } } case 'setSecretsProperty': { - const { key, value } = payload; + const { key, value } = action.payload as TPayload; if (isEqual(connector.secrets[key], value)) { return state; } else { @@ -78,7 +120,7 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => { connector: { ...connector, secrets: { - ...connector.secrets, + ...(connector.secrets as Secrets), [key]: value, }, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 0cf859ad641739..f4dc4c3d4ef265 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -43,15 +43,16 @@ export { ActionType }; export type ActionTypeIndex = Record; export type AlertTypeIndex = Map; -export type ActionTypeRegistryContract = PublicMethodsOf< - TypeRegistry> ->; +export type ActionTypeRegistryContract< + ActionConnector = unknown, + ActionParams = unknown +> = PublicMethodsOf>>; export type AlertTypeRegistryContract = PublicMethodsOf>; export interface ActionConnectorFieldsProps { action: TActionConnector; - editActionConfig: (property: string, value: any) => void; - editActionSecrets: (property: string, value: any) => void; + editActionConfig: (property: string, value: unknown) => void; + editActionSecrets: (property: string, value: unknown) => void; errors: IErrorObject; readOnly: boolean; consumer?: string; @@ -128,13 +129,13 @@ export type UserConfiguredActionConnector = ActionConnectorProp isPreconfigured: false; }; -export type ActionConnector, Secrets = Record> = +export type ActionConnector, Secrets = Record> = | PreConfiguredActionConnector | UserConfiguredActionConnector; export type ActionConnectorWithoutId< - Config = Record, - Secrets = Record + Config = Record, + Secrets = Record > = Omit, 'id'>; export type ActionConnectorTableItem = ActionConnector & { @@ -186,7 +187,7 @@ export interface AlertTableItem extends Alert { export interface AlertTypeParamsExpressionProps< Params extends AlertTypeParams = AlertTypeParams, - MetaData = Record, + MetaData = Record, ActionGroupIds extends string = string > { alertParams: Params; diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 1d287447461e63..f2d91c2ae577f9 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -74,7 +74,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.configureDimension({ dimension: 'lnsXY_splitDimensionPanel > lns-dimensionTrigger', operation: 'filters', - isPreviousIncompatible: true, keepOpen: true, }); await PageObjects.lens.addFilterToAgg(`geo.src : CN`); @@ -478,6 +477,43 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); + it('should keep the field selection while transitioning to every reference-based operation', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'avg', + field: 'bytes', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'counter_rate', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'cumulative_sum', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'derivative', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'moving_average', + }); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + 'Moving average of Sum of bytes' + ); + }); + it('should allow to change index pattern', async () => { await PageObjects.lens.switchFirstLayerIndexPattern('log*'); expect(await PageObjects.lens.getFirstLayerIndexPattern()).to.equal('log*'); diff --git a/x-pack/test/functional/apps/maps/es_geo_grid_source.js b/x-pack/test/functional/apps/maps/es_geo_grid_source.js index 19680ae851a34d..12af15793ff9a0 100644 --- a/x-pack/test/functional/apps/maps/es_geo_grid_source.js +++ b/x-pack/test/functional/apps/maps/es_geo_grid_source.js @@ -13,8 +13,6 @@ export default function ({ getPageObjects, getService }) { const security = getService('security'); describe('layer geo grid aggregation source', () => { - const EXPECTED_NUMBER_FEATURES_ZOOMED_OUT = 4; - const EXPECTED_NUMBER_FEATURES_ZOOMED_IN = 6; const DATA_CENTER_LON = -98; const DATA_CENTER_LAT = 38; @@ -41,7 +39,11 @@ export default function ({ getPageObjects, getService }) { return requestTimestamp; } - function makeRequestTestsForGeoPrecision(LAYER_ID) { + function makeRequestTestsForGeoPrecision( + LAYER_ID, + expectedNumFeaturesZoomedOut, + expectedNumPartialFeatures + ) { describe('geoprecision - requests', () => { let beforeTimestamp; beforeEach(async () => { @@ -84,7 +86,7 @@ export default function ({ getPageObjects, getService }) { it('should request the data when the map covers the databounds', async () => { const mapboxStyle = await PageObjects.maps.getMapboxStyle(); expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal( - EXPECTED_NUMBER_FEATURES_ZOOMED_OUT + expectedNumFeaturesZoomedOut ); }); @@ -92,7 +94,9 @@ export default function ({ getPageObjects, getService }) { //todo this verifies the extent-filtering behavior (not really the correct application of geotile_grid-precision), and should ideally be moved to its own section await PageObjects.maps.setView(DATA_CENTER_LAT, DATA_CENTER_LON, 6); const mapboxStyle = await PageObjects.maps.getMapboxStyle(); - expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal(2); + expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal( + expectedNumPartialFeatures + ); }); }); } @@ -115,9 +119,7 @@ export default function ({ getPageObjects, getService }) { it('should decorate feature properties with scaled doc_count property', async () => { const mapboxStyle = await PageObjects.maps.getMapboxStyle(); - expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal( - EXPECTED_NUMBER_FEATURES_ZOOMED_IN - ); + expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal(6); mapboxStyle.sources[LAYER_ID].data.features.forEach(({ properties }) => { expect(properties.hasOwnProperty(HEATMAP_PROP_NAME)).to.be(true); @@ -125,7 +127,7 @@ export default function ({ getPageObjects, getService }) { }); }); - makeRequestTestsForGeoPrecision(LAYER_ID); + makeRequestTestsForGeoPrecision(LAYER_ID, 4, 2); describe('query bar', () => { before(async () => { @@ -194,9 +196,7 @@ export default function ({ getPageObjects, getService }) { it('should decorate feature properties with metrics properterties', async () => { const mapboxStyle = await PageObjects.maps.getMapboxStyle(); - expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal( - EXPECTED_NUMBER_FEATURES_ZOOMED_IN - ); + expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal(12); mapboxStyle.sources[LAYER_ID].data.features.forEach(({ properties }) => { expect(properties.hasOwnProperty(MAX_OF_BYTES_PROP_NAME)).to.be(true); @@ -204,7 +204,7 @@ export default function ({ getPageObjects, getService }) { }); }); - makeRequestTestsForGeoPrecision(LAYER_ID); + makeRequestTestsForGeoPrecision(LAYER_ID, 8, 4); describe('query bar', () => { before(async () => { @@ -262,7 +262,7 @@ export default function ({ getPageObjects, getService }) { const LAYER_ID = 'g1xkv'; it('should get expected number of grid cells', async () => { const mapboxStyle = await PageObjects.maps.getMapboxStyle(); - expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal(13); + expect(mapboxStyle.sources[LAYER_ID].data.features.length).to.equal(26); }); describe('inspector', () => { diff --git a/x-pack/test/functional/apps/maps/es_pew_pew_source.js b/x-pack/test/functional/apps/maps/es_pew_pew_source.js index b0f98f807fd44b..7c6ca3f516062a 100644 --- a/x-pack/test/functional/apps/maps/es_pew_pew_source.js +++ b/x-pack/test/functional/apps/maps/es_pew_pew_source.js @@ -38,7 +38,7 @@ export default function ({ getPageObjects, getService }) { it('should render lines', async () => { const mapboxStyle = await PageObjects.maps.getMapboxStyle(); const features = mapboxStyle.sources[VECTOR_SOURCE_ID].data.features; - expect(features.length).to.equal(2); + expect(features.length).to.equal(4); expect(features[0].geometry.type).to.equal('LineString'); }); diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index 9c769b8d9f59de..ff6686eef53ab6 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -81,7 +81,7 @@ export default function ({ getPageObjects, getService }) { it('should decorate feature properties with join property', async () => { const mapboxStyle = await PageObjects.maps.getMapboxStyle(); - expect(mapboxStyle.sources[VECTOR_SOURCE_ID].data.features.length).to.equal(4); + expect(mapboxStyle.sources[VECTOR_SOURCE_ID].data.features.length).to.equal(8); mapboxStyle.sources.n1t6f.data.features.forEach(({ properties }) => { if (properties.name === 'tango') { @@ -130,7 +130,17 @@ export default function ({ getPageObjects, getService }) { return feature.properties.__kbn_isvisibleduetojoin__; }); - expect(visibilitiesOfFeatures).to.eql([false, true, true, true]); + expect(visibilitiesOfFeatures).to.eql([ + false, + true, + true, + true, + // geo centroids for above features + false, + true, + true, + true, + ]); }); describe('query bar', () => { @@ -196,7 +206,17 @@ export default function ({ getPageObjects, getService }) { return feature.properties.__kbn_isvisibleduetojoin__; }); - expect(visibilitiesOfFeatures).to.eql([false, true, false, false]); + expect(visibilitiesOfFeatures).to.eql([ + false, + true, + false, + false, + // geo centroids for above features + false, + true, + false, + false, + ]); }); }); diff --git a/x-pack/test/functional/apps/maps/mapbox_styles.js b/x-pack/test/functional/apps/maps/mapbox_styles.js index 78720fa1689ec6..ed94b2290af638 100644 --- a/x-pack/test/functional/apps/maps/mapbox_styles.js +++ b/x-pack/test/functional/apps/maps/mapbox_styles.js @@ -17,6 +17,7 @@ export const MAPBOX_STYLES = { [ 'all', ['!=', ['get', '__kbn_too_many_features__'], true], + ['!=', ['get', '__kbn_is_centroid_feature__'], true], ['any', ['==', ['geometry-type'], 'Point'], ['==', ['geometry-type'], 'MultiPoint']], ], ], @@ -91,6 +92,7 @@ export const MAPBOX_STYLES = { [ 'all', ['!=', ['get', '__kbn_too_many_features__'], true], + ['!=', ['get', '__kbn_is_centroid_feature__'], true], ['any', ['==', ['geometry-type'], 'Polygon'], ['==', ['geometry-type'], 'MultiPolygon']], ], ], @@ -161,6 +163,7 @@ export const MAPBOX_STYLES = { [ 'all', ['!=', ['get', '__kbn_too_many_features__'], true], + ['!=', ['get', '__kbn_is_centroid_feature__'], true], [ 'any', ['==', ['geometry-type'], 'Polygon'], diff --git a/x-pack/test/plugin_functional/screenshots/baseline/first_child.png b/x-pack/test/plugin_functional/screenshots/baseline/first_child.png index 2d9fd1f039119f..54385625951bd6 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/first_child.png and b/x-pack/test/plugin_functional/screenshots/baseline/first_child.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected.png b/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected.png index b5b7644e3ccf5a..472bee1ad0845a 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected.png and b/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected_with_primary_button_hovered.png b/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected_with_primary_button_hovered.png index b73aa39c03e7ae..472bee1ad0845a 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected_with_primary_button_hovered.png and b/x-pack/test/plugin_functional/screenshots/baseline/first_child_selected_with_primary_button_hovered.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/first_child_with_primary_button_hovered.png b/x-pack/test/plugin_functional/screenshots/baseline/first_child_with_primary_button_hovered.png index 9eacdc920bacd9..a49aa86d029b4e 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/first_child_with_primary_button_hovered.png and b/x-pack/test/plugin_functional/screenshots/baseline/first_child_with_primary_button_hovered.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/origin.png b/x-pack/test/plugin_functional/screenshots/baseline/origin.png index 5df290b3a2cff9..889101f961a351 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/origin.png and b/x-pack/test/plugin_functional/screenshots/baseline/origin.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected.png b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected.png index e1da213120fb59..20fccf0c3269e4 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected.png and b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_hovered.png b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_hovered.png index e1da213120fb59..20fccf0c3269e4 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_hovered.png and b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_hovered.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_selected.png b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_selected.png index 85de76fadb3241..4bf850885a9cb5 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_selected.png and b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_first_pill_selected.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_primary_button_hovered.png b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_primary_button_hovered.png index b1fd64d9f0b700..be607c06df3e81 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_primary_button_hovered.png and b/x-pack/test/plugin_functional/screenshots/baseline/origin_selected_with_primary_button_hovered.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/origin_with_primary_button_hovered.png b/x-pack/test/plugin_functional/screenshots/baseline/origin_with_primary_button_hovered.png index ac3892e4920586..be607c06df3e81 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/origin_with_primary_button_hovered.png and b/x-pack/test/plugin_functional/screenshots/baseline/origin_with_primary_button_hovered.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/second_child.png b/x-pack/test/plugin_functional/screenshots/baseline/second_child.png index 41b34be569ce92..3746098cad000e 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/second_child.png and b/x-pack/test/plugin_functional/screenshots/baseline/second_child.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected.png b/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected.png index 59277eb63f85fb..4577c28d9bef4b 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected.png and b/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected_with_primary_button_hovered.png b/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected_with_primary_button_hovered.png index 5eb5828f5a74d0..ad84e5e781420b 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected_with_primary_button_hovered.png and b/x-pack/test/plugin_functional/screenshots/baseline/second_child_selected_with_primary_button_hovered.png differ diff --git a/x-pack/test/plugin_functional/screenshots/baseline/second_child_with_primary_button_hovered.png b/x-pack/test/plugin_functional/screenshots/baseline/second_child_with_primary_button_hovered.png index 3854dc2c3e0feb..037525b92eab23 100644 Binary files a/x-pack/test/plugin_functional/screenshots/baseline/second_child_with_primary_button_hovered.png and b/x-pack/test/plugin_functional/screenshots/baseline/second_child_with_primary_button_hovered.png differ diff --git a/x-pack/test/plugin_functional/test_suites/resolver/index.ts b/x-pack/test/plugin_functional/test_suites/resolver/index.ts index 5b50afb4017935..437b279647eb07 100644 --- a/x-pack/test/plugin_functional/test_suites/resolver/index.ts +++ b/x-pack/test/plugin_functional/test_suites/resolver/index.ts @@ -26,7 +26,7 @@ export default function ({ const browser = getService('browser'); // FLAKY: https://github.com/elastic/kibana/issues/87425 - describe.skip('Resolver test app', function () { + describe('Resolver test app', function () { this.tags('ciGroup7'); // Note: these tests are intended to run on the same page in serial. diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index e344d4c3c27e49..f53c1c589daabe 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -67,6 +67,42 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); + describe('on the Malware protections section', () => { + let policyInfo: PolicyTestResourceInfo; + + beforeEach(async () => { + policyInfo = await policyTestResources.createPolicy(); + await pageObjects.policy.navigateToPolicyDetails(policyInfo.packagePolicy.id); + await testSubjects.existOrFail('malwareProtectionsForm'); + }); + + afterEach(async () => { + if (policyInfo) { + await policyInfo.cleanup(); + } + }); + + it('should show the custom message text area when the Notify User checkbox is checked', async () => { + expect(await testSubjects.isChecked('malwareUserNotificationCheckbox')).to.be(true); + await testSubjects.existOrFail('malwareUserNotificationCustomMessage'); + }); + it('should not show the custom message text area when the Notify User checkbox is unchecked', async () => { + await pageObjects.endpointPageUtils.clickOnEuiCheckbox('malwareUserNotificationCheckbox'); + expect(await testSubjects.isChecked('malwareUserNotificationCheckbox')).to.be(false); + await testSubjects.missingOrFail('malwareUserNotificationCustomMessage'); + }); + it('should preserve a custom notification message upon saving', async () => { + const customMessage = await testSubjects.find('malwareUserNotificationCustomMessage'); + await customMessage.clearValue(); + await customMessage.type('a custom malware notification message'); + await pageObjects.policy.confirmAndSave(); + await testSubjects.existOrFail('policyDetailsSuccessMessage'); + expect(await testSubjects.getVisibleText('malwareUserNotificationCustomMessage')).to.equal( + 'a custom malware notification message' + ); + }); + }); + describe('and the save button is clicked', () => { let policyInfo: PolicyTestResourceInfo; diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 5b05628d618a7d..b67171f50859a4 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -32,6 +32,7 @@ { "path": "../plugins/features/tsconfig.json" }, { "path": "../plugins/embeddable_enhanced/tsconfig.json" }, { "path": "../plugins/licensing/tsconfig.json" }, + { "path": "../plugins/task_manager/tsconfig.json" }, { "path": "../plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "../plugins/ui_actions_enhanced/tsconfig.json" }, ] diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index f6911d12031047..1182732e64673d 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -10,6 +10,7 @@ "plugins/embeddable_enhanced/**/*", "plugins/licensing/**/*", "plugins/security_solution/cypress/**/*", + "plugins/task_manager/**/*", "plugins/telemetry_collection_xpack/**/*", "plugins/ui_actions_enhanced/**/*", "test/**/*" @@ -48,6 +49,7 @@ { "path": "./plugins/features/tsconfig.json" }, { "path": "./plugins/embeddable_enhanced/tsconfig.json" }, { "path": "./plugins/licensing/tsconfig.json" }, + { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" } ] diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json index 0516f414963efd..d5012df00beb05 100644 --- a/x-pack/tsconfig.refs.json +++ b/x-pack/tsconfig.refs.json @@ -6,6 +6,7 @@ { "path": "./plugins/global_search/tsconfig.json" }, { "path": "./plugins/features/tsconfig.json" }, { "path": "./plugins/embeddable_enhanced/tsconfig.json" }, + { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, ] diff --git a/yarn.lock b/yarn.lock index 6ade7d732ad5fb..765eef28dec771 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4329,6 +4329,25 @@ dependencies: "@babel/runtime" "^7.10.2" +"@turf/along@6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@turf/along/-/along-6.0.1.tgz#595cecdc48fc7fcfa83c940a8e3eb24d4c2e04d4" + integrity sha512-6PptAcrsFR3o0Flpktk8Vo68W2txEVTh14zjoTVu+H5docd2+pv5/upA77bg3YFBoJgAxmUFt1leDdjReJ44BQ== + dependencies: + "@turf/bearing" "6.x" + "@turf/destination" "6.x" + "@turf/distance" "6.x" + "@turf/helpers" "6.x" + "@turf/invariant" "6.x" + +"@turf/area@6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@turf/area/-/area-6.0.1.tgz#50ed63c70ef2bdb72952384f1594319d94f3b051" + integrity sha512-Zv+3N1ep9P5JvR0YOYagLANyapGWQBh8atdeR3bKpWcigVXFsEKNUw03U/5xnh+cKzm7yozHD6MFJkqQv55y0g== + dependencies: + "@turf/helpers" "6.x" + "@turf/meta" "6.x" + "@turf/bbox-polygon@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@turf/bbox-polygon/-/bbox-polygon-6.0.1.tgz#ae0fbb14558831fb34538aae089a23d3336c6379" @@ -4344,6 +4363,14 @@ "@turf/helpers" "6.x" "@turf/meta" "6.x" +"@turf/bearing@6.x": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@turf/bearing/-/bearing-6.0.1.tgz#8da5d17092e571f170cde7bfb2e5b0d74923c92d" + integrity sha512-mXY1NozqV9EFfBTbUItujwfqfQF0G/Xe2fzvnZle90ekPEUfhi4Dgf5JswJTd96J9LiT8kcd6Jonp5khnx0wIg== + dependencies: + "@turf/helpers" "6.x" + "@turf/invariant" "6.x" + "@turf/boolean-contains@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@turf/boolean-contains/-/boolean-contains-6.0.1.tgz#c3c583215fc5bda47ede51cf52d735ffdc1006a5" @@ -4371,6 +4398,25 @@ "@turf/helpers" "6.x" "@turf/invariant" "6.x" +"@turf/center-of-mass@6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@turf/center-of-mass/-/center-of-mass-6.0.1.tgz#be8904edfd6523683706429ea2f4adf5badd5b26" + integrity sha512-cY+RndzVzDBMlEShRmvLko0CSG1+iC+WdeMAtauCGL61e23LTYHxFSjVOOo4gF+aKqKia1veZPol8ENJoOU4ow== + dependencies: + "@turf/centroid" "6.x" + "@turf/convex" "6.x" + "@turf/helpers" "6.x" + "@turf/invariant" "6.x" + "@turf/meta" "6.x" + +"@turf/centroid@6.x": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@turf/centroid/-/centroid-6.0.2.tgz#c4eb16b4bc60b692f74e1809cf9a7c4a4f5ba1cc" + integrity sha512-auyDauOtC4eddH7GC3CHFTDu2PKhpSeKCRhwhHhXtJqn2dWCJQNIoCeJRmfXRIbzCWhWvgvQafvvhq8HNvmvWw== + dependencies: + "@turf/helpers" "6.x" + "@turf/meta" "6.x" + "@turf/circle@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@turf/circle/-/circle-6.0.1.tgz#0ab72083373ae3c76b700c17a504ab1b5c0910b9" @@ -4379,6 +4425,15 @@ "@turf/destination" "6.x" "@turf/helpers" "6.x" +"@turf/convex@6.x": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@turf/convex/-/convex-6.0.3.tgz#d7e9912b96483f1504cdd2f60b4b1bbdbf77416c" + integrity sha512-S9zvcKiqkIiQ/fhnEP5ftDrsVY3Sh0XeLDVZY761nlvuvzLVzz26Gq7H3NMsCJlmIcQS9jPARFBVpRZi6eTV8Q== + dependencies: + "@turf/helpers" "6.x" + "@turf/meta" "6.x" + concaveman "*" + "@turf/destination@6.x": version "6.0.1" resolved "https://registry.yarnpkg.com/@turf/destination/-/destination-6.0.1.tgz#5275887fa96ec463f44864a2c17f0b712361794a" @@ -4387,7 +4442,7 @@ "@turf/helpers" "6.x" "@turf/invariant" "6.x" -"@turf/distance@6.0.1": +"@turf/distance@6.0.1", "@turf/distance@6.x": version "6.0.1" resolved "https://registry.yarnpkg.com/@turf/distance/-/distance-6.0.1.tgz#0761f28784286e7865a427c4e7e3593569c2dea8" integrity sha512-q7t7rWIWfkg7MP1Vt4uLjSEhe5rPfCO2JjpKmk7JC+QZKEQkuvHEqy3ejW1iC7Kw5ZcZNR3qdMGGz+6HnVwqvg== @@ -4407,6 +4462,15 @@ dependencies: "@turf/helpers" "6.x" +"@turf/length@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@turf/length/-/length-6.0.2.tgz#22d91a6d0174e862a3614865613f1aceb1162dac" + integrity sha512-nyfXMowVtX2dICEG7u7EGC2SMaauVUWIMc9eWQrEauNA/9aw+7wbiuip4GPBoyeXEUUekF0EOjJn5aB9Zc8CzA== + dependencies: + "@turf/distance" "6.x" + "@turf/helpers" "6.x" + "@turf/meta" "6.x" + "@turf/meta@6.x": version "6.0.2" resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-6.0.2.tgz#eb92951126d24a613ac1b7b99d733fcc20fd30cf" @@ -9955,6 +10019,17 @@ concat-stream@~2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" +concaveman@*: + version "1.1.1" + resolved "https://registry.yarnpkg.com/concaveman/-/concaveman-1.1.1.tgz#6c2482580b2523cef82fc2bec00a0415e6e68162" + integrity sha1-bCSCWAslI874L8K+wAoEFebmgWI= + dependencies: + monotone-convex-hull-2d "^1.0.1" + point-in-polygon "^1.0.1" + rbush "^2.0.1" + robust-orientation "^1.1.3" + tinyqueue "^1.1.0" + config-chain@^1.1.12, config-chain@~1.1.8: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -11574,10 +11649,10 @@ detective@^5.0.2, detective@^5.2.0: defined "^1.0.0" minimist "^1.1.1" -devtools-protocol@0.0.818844: - version "0.0.818844" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.818844.tgz#d1947278ec85b53e4c8ca598f607a28fa785ba9e" - integrity sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg== +devtools-protocol@0.0.809251: + version "0.0.809251" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.809251.tgz#300b3366be107d5c46114ecb85274173e3999518" + integrity sha512-pf+2OY6ghMDPjKkzSWxHMq+McD+9Ojmq5XVRYpv/kPd9sTMQxzEt21592a31API8qRjro0iYYOc3ag46qF/1FA== dezalgo@^1.0.0: version "1.0.3" @@ -20122,6 +20197,13 @@ monocle-ts@^1.0.0: resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-1.7.1.tgz#03a615938aa90983a4fa29749969d30f72d80ba1" integrity sha512-X9OzpOyd/R83sYex8NYpJjUzi/MLQMvGNVfxDYiIvs+QMXMEUDwR61MQoARFN10Cqz5h/mbFSPnIQNUIGhYd2Q== +monotone-convex-hull-2d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/monotone-convex-hull-2d/-/monotone-convex-hull-2d-1.0.1.tgz#47f5daeadf3c4afd37764baa1aa8787a40eee08c" + integrity sha1-R/Xa6t88Sv03dkuqGqh4ekDu4Iw= + dependencies: + robust-orientation "^1.1.3" + moo@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" @@ -22091,6 +22173,11 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" +point-in-polygon@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/point-in-polygon/-/point-in-polygon-1.0.1.tgz#d59b64e8fee41c49458aac82b56718c5957b2af7" + integrity sha1-1Ztk6P7kHElFiqyCtWcYxZV7Kvc= + polished@^1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.2.tgz#d705cac66f3a3ed1bd38aad863e2c1e269baf6b6" @@ -22589,7 +22676,7 @@ puppeteer@^2.0.0: integrity sha512-I4JbNmQHZkE72TPNdipND8GnsEBnqzuksxPSAT25qvudShuuzdY9TwNBQ65IJwPD/pjlpx7fUIUmFyvTHwlxhQ== dependencies: debug "^4.1.0" - devtools-protocol "0.0.818844" + devtools-protocol "0.0.809251" extract-zip "^2.0.0" https-proxy-agent "^4.0.0" node-fetch "^2.6.1" @@ -22665,6 +22752,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quickselect@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-1.1.1.tgz#852e412ce418f237ad5b660d70cffac647ae94c2" + integrity sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ== + quickselect@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" @@ -22775,6 +22867,13 @@ raw-loader@^4.0.1: loader-utils "^2.0.0" schema-utils "^2.6.5" +rbush@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/rbush/-/rbush-2.0.2.tgz#bb6005c2731b7ba1d5a9a035772927d16a614605" + integrity sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA== + dependencies: + quickselect "^1.0.1" + rbush@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf" @@ -24609,6 +24708,34 @@ rison-node@1.0.2: resolved "https://registry.yarnpkg.com/rison-node/-/rison-node-1.0.2.tgz#b7b5f37f39f5ae2a51a973a33c9aa17239a33e4b" integrity sha1-t7Xzfzn1ripRqXOjPJqhcjmjPks= +robust-orientation@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/robust-orientation/-/robust-orientation-1.1.3.tgz#daff5b00d3be4e60722f0e9c0156ef967f1c2049" + integrity sha1-2v9bANO+TmByLw6cAVbvln8cIEk= + dependencies: + robust-scale "^1.0.2" + robust-subtract "^1.0.0" + robust-sum "^1.0.0" + two-product "^1.0.2" + +robust-scale@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/robust-scale/-/robust-scale-1.0.2.tgz#775132ed09542d028e58b2cc79c06290bcf78c32" + integrity sha1-d1Ey7QlULQKOWLLMecBikLz3jDI= + dependencies: + two-product "^1.0.2" + two-sum "^1.0.0" + +robust-subtract@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/robust-subtract/-/robust-subtract-1.0.0.tgz#e0b164e1ed8ba4e3a5dda45a12038348dbed3e9a" + integrity sha1-4LFk4e2LpOOl3aRaEgODSNvtPpo= + +robust-sum@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/robust-sum/-/robust-sum-1.0.0.tgz#16646e525292b4d25d82757a286955e0bbfa53d9" + integrity sha1-FmRuUlKStNJdgnV6KGlV4Lv6U9k= + rollup@^0.25.8: version "0.25.8" resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.25.8.tgz#bf6ce83b87510d163446eeaa577ed6a6fc5835e0" @@ -26929,6 +27056,11 @@ tinymath@1.2.1: resolved "https://registry.yarnpkg.com/tinymath/-/tinymath-1.2.1.tgz#f97ed66c588cdbf3c19dfba2ae266ee323db7e47" integrity sha512-8CYutfuHR3ywAJus/3JUhaJogZap1mrUQGzNxdBiQDhP3H0uFdQenvaXvqI8lMehX4RsanRZzxVfjMBREFdQaA== +tinyqueue@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-1.2.3.tgz#b6a61de23060584da29f82362e45df1ec7353f3d" + integrity sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA== + tinyqueue@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" @@ -27329,6 +27461,16 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +two-product@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/two-product/-/two-product-1.0.2.tgz#67d95d4b257a921e2cb4bd7af9511f9088522eaa" + integrity sha1-Z9ldSyV6kh4stL16+VEfkIhSLqo= + +two-sum@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/two-sum/-/two-sum-1.0.0.tgz#31d3f32239e4f731eca9df9155e2b297f008ab64" + integrity sha1-MdPzIjnk9zHsqd+RVeKyl/AIq2Q= + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"