diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 23ebc16c8d922..41b3617cc52b9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -493,6 +493,7 @@ examples/guided_onboarding_example @elastic/appex-sharedux src/plugins/guided_onboarding @elastic/appex-sharedux packages/kbn-handlebars @elastic/kibana-security packages/kbn-hapi-mocks @elastic/kibana-core +test/plugin_functional/plugins/hardening @elastic/kibana-security packages/kbn-health-gateway-server @elastic/kibana-core examples/hello_world @elastic/kibana-core src/plugins/home @elastic/kibana-core @@ -1351,7 +1352,9 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /packages/kbn-std/src/parse_next_url.ts @elastic/kibana-core @elastic/kibana-security /test/interactive_setup_api_integration/ @elastic/kibana-security /test/interactive_setup_functional/ @elastic/kibana-security +/test/plugin_functional/plugins/hardening @elastic/kibana-security /test/plugin_functional/test_suites/core_plugins/rendering.ts @elastic/kibana-security +/test/plugin_functional/test_suites/hardening @elastic/kibana-security /x-pack/test/accessibility/apps/group1/login_page.ts @elastic/kibana-security /x-pack/test/accessibility/apps/group1/roles.ts @elastic/kibana-security /x-pack/test/accessibility/apps/group1/spaces.ts @elastic/kibana-security diff --git a/config/serverless.security.yml b/config/serverless.security.yml index 6f39182d1e2e6..b5922379f3c4b 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -106,3 +106,8 @@ xpack.ml.compatibleModuleType: 'security' # Disable the embedded Dev Console console.ui.embeddedEnabled: false + +# mTLS cert paths for agentless-api calls +xpack.fleet.agentless.api.tls.certificate: "/mnt/elastic-internal/http-certs/tls.crt" +xpack.fleet.agentless.api.tls.key: "/mnt/elastic-internal/http-certs/tls.key" +xpack.fleet.agentless.api.tls.ca: "/mnt/elastic-internal/http-certs/ca.crt" diff --git a/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx b/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx index 1c1224c1c858a..37322e3f55e05 100644 --- a/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx +++ b/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx @@ -294,6 +294,58 @@ This event will be indexed with the following structure: } ``` +#### Add custom metrics +Having `kibana:plugin_render_time` metric event is not always enough, depending on the use case you would likely need some complementary information to give some sense to the value reported by the metric (e.g. number of hosts, number of services, number of dataStreams, etc). +`kibana:plugin_render_time` metric API supports up to 9 numbered free fields that can be used to report numeric metrics that you intend to analyze. Note that they can be used for any type of numeric information you may want to report. + +We could make use of these custom metrics using the following format: + +```typescript +... + // Call onPageReady once the meaningful data has rendered and visible to the user + onPageReady({ + key1: 'datasets', + value1: 5, + key2: 'documents', + value2: 1000, + }); +... +``` + +where the `keys` will be the keys for the custom metrics we can later aggregate and analyze further. + +An event using custom metrics will be indexed with the following structure: + +```typescript +{ + "_index": "backing-ebt-kibana-browser-performance-metrics-000001", // Performance metrics are stored in a dedicated simplified index (browser \ server). + "_source": { + "timestamp": "2024-08-13T11:29:58.275Z" + "event_type": "performance_metric", // All performance events share a common event type to simplify mapping + "eventName": 'kibana:plugin_render_time', // Event name as specified when reporting it + "duration": 736, // Event duration as specified when reporting it + "meta": { + "target": '/home', + }, + "context": { // Context holds information identifying the deployment, version, application and page that generated the event + "version": "8.16.0-SNAPSHOT", + "cluster_name": "elasticsearch", + "pageName": "application:home:app", + "applicationId": "home", + "page": "app", + "entityId": "61c58ad0-3dd3-11e8-b2b9-5d5dc1715159", + "branch": "main", + ... + }, + "key1": "datasets", + "value1": 5, + "key2": "documents", + "value2": 1000, + ... + }, +} +``` + ### Development environment The metric will be delivered to the [Telemetry Staging](https://telemetry-v2-staging.elastic.dev/) cluster, alongside with the event's context. diff --git a/package.json b/package.json index cf7781fd6a2f0..5ece731ab84d0 100644 --- a/package.json +++ b/package.json @@ -539,6 +539,7 @@ "@kbn/guided-onboarding-plugin": "link:src/plugins/guided_onboarding", "@kbn/handlebars": "link:packages/kbn-handlebars", "@kbn/hapi-mocks": "link:packages/kbn-hapi-mocks", + "@kbn/hardening-plugin": "link:test/plugin_functional/plugins/hardening", "@kbn/health-gateway-server": "link:packages/kbn-health-gateway-server", "@kbn/hello-world-plugin": "link:examples/hello_world", "@kbn/home-plugin": "link:src/plugins/home", @@ -1294,10 +1295,10 @@ "@frsource/cypress-plugin-visual-regression-diff": "^3.3.10", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", - "@jest/console": "^29.6.1", - "@jest/reporters": "^29.6.1", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", "@jest/transform": "^29.6.1", - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@kayahr/text-encoding": "^1.3.0", "@kbn/alerting-api-integration-helpers": "link:x-pack/test/alerting_api_integration/packages/helpers", "@kbn/ambient-common-types": "link:packages/kbn-ambient-common-types", @@ -1631,7 +1632,7 @@ "argsplit": "^1.0.5", "autoprefixer": "^10.4.7", "axe-core": "^4.10.0", - "babel-jest": "^29.6.1", + "babel-jest": "^29.7.0", "babel-loader": "^8.2.5", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-istanbul": "^6.1.1", @@ -1685,7 +1686,7 @@ "eslint-plugin-react-perf": "^3.3.1", "eslint-traverse": "^1.0.0", "exit-hook": "^2.2.0", - "expect": "^29.6.1", + "expect": "^29.7.0", "expose-loader": "^0.7.5", "express": "^4.19.2", "faker": "^5.1.0", @@ -1704,16 +1705,16 @@ "http2-proxy": "^5.0.53", "http2-wrapper": "^2.2.1", "ignore": "^5.3.0", - "jest": "^29.6.1", + "jest": "^29.7.0", "jest-canvas-mock": "^2.5.2", - "jest-cli": "^29.6.1", - "jest-config": "^29.6.1", - "jest-diff": "^29.6.1", - "jest-environment-jsdom": "^29.6.1", - "jest-matcher-utils": "^29.6.1", - "jest-mock": "^29.6.1", - "jest-runtime": "^29.6.1", - "jest-snapshot": "^29.6.1", + "jest-cli": "^29.7.0", + "jest-config": "^29.7.0", + "jest-diff": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", "jest-specific-snapshot": "^8.0.0", "jest-styled-components": "7.0.3", "jsdom": "^20.0.1", diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/session_manager.test.ts similarity index 98% rename from packages/kbn-test/src/auth/sesson_manager.test.ts rename to packages/kbn-test/src/auth/session_manager.test.ts index 929517e7c5a10..fe5e0e9fb9198 100644 --- a/packages/kbn-test/src/auth/sesson_manager.test.ts +++ b/packages/kbn-test/src/auth/session_manager.test.ts @@ -28,7 +28,6 @@ const roleEditor = 'editor'; const cloudUsersFilePath = resolve(REPO_ROOT, SERVERLESS_ROLES_ROOT_PATH, 'role_users.json'); const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession'); -const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); const getSecurityProfileMock = jest.spyOn(samlAuth, 'getSecurityProfile'); const readCloudUsersFromFileMock = jest.spyOn(helper, 'readCloudUsersFromFile'); const isValidHostnameMock = jest.spyOn(helper, 'isValidHostname'); @@ -41,6 +40,11 @@ jest.mock('../kbn_client/kbn_client', () => { const get = jest.fn(); describe('SamlSessionManager', () => { + let createCloudSAMLSessionMock: jest.SpyInstance; + beforeEach(() => { + createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); + }); + describe('for local session', () => { beforeEach(() => { jest.resetAllMocks(); @@ -199,6 +203,7 @@ describe('SamlSessionManager', () => { }); test('should throw error if TEST_CLOUD_HOST_NAME is not set', async () => { + createCloudSAMLSessionMock.mockRestore(); isValidHostnameMock.mockReturnValueOnce(false); const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await expect( diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 75237f0ea3594..e2ffab235f34e 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -412,7 +412,11 @@ kibana_vars=( xpack.securitySolution.packagerTaskInterval xpack.securitySolution.prebuiltRulesPackageVersion xpack.spaces.maxSpaces + xpack.task_manager.capacity xpack.task_manager.claim_strategy + xpack.task_manager.discovery.active_nodes_lookback + xpack.task_manager.discovery.interval + xpack.task_manager.kibanas_per_partition xpack.task_manager.max_attempts xpack.task_manager.max_workers xpack.task_manager.monitored_aggregated_stats_refresh_rate diff --git a/src/plugins/files/server/blob_storage_service/adapters/es/integration_tests/es.test.ts b/src/plugins/files/server/blob_storage_service/adapters/es/integration_tests/es.test.ts index 981810e968f65..922c84fa015d3 100644 --- a/src/plugins/files/server/blob_storage_service/adapters/es/integration_tests/es.test.ts +++ b/src/plugins/files/server/blob_storage_service/adapters/es/integration_tests/es.test.ts @@ -107,7 +107,7 @@ describe('Elasticsearch blob storage', () => { esBlobStorage = createEsBlobStorage({ chunkSize: '1024B' }); const { id } = await esBlobStorage.upload(Readable.from([fileString])); expect(await getAllDocCount()).toMatchObject({ count: 37 }); - esRefreshIndexSpy.mockReset(); + esRefreshIndexSpy.mockClear(); const rs = await esBlobStorage.download({ id }); expect(esRefreshIndexSpy).toHaveBeenCalled(); // Make sure we refresh the index before downloading the chunks const chunks: string[] = []; @@ -141,7 +141,7 @@ describe('Elasticsearch blob storage', () => { const { id } = await esBlobStorage.upload(Readable.from([fileString])); const { id: id2 } = await esBlobStorage.upload(Readable.from([fileString2])); expect(await getAllDocCount()).toMatchObject({ count: 10 }); - esRefreshIndexSpy.mockReset(); + esRefreshIndexSpy.mockClear(); await esBlobStorage.delete(id); expect(esRefreshIndexSpy).toHaveBeenCalled(); // Make sure we refresh the index before deleting the chunks expect(await getAllDocCount()).toMatchObject({ count: 2 }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile.test.ts index 885a2b16b44cc..e8f8fe266f78a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile.test.ts @@ -226,7 +226,9 @@ describe('convertToPercentileColumns', () => { if (expected === null) { expect(convertToPercentileColumns(...input)).toBeNull(); } else if (Array.isArray(expected)) { - expect(convertToPercentileColumns(...input)).toEqual(expected.map(expect.objectContaining)); + expect(convertToPercentileColumns(...input)).toEqual( + expected.map((el) => (el === null ? null : expect.objectContaining(el))) + ); } else { expect(convertToPercentileColumns(...input)).toEqual(expect.objectContaining(expected)); } diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile_rank.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile_rank.test.ts index 25c047f8a19be..a1b09b57119b2 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile_rank.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/percentile_rank.test.ts @@ -202,7 +202,7 @@ describe('convertToPercentileRankColumns', () => { expect(convertToPercentileRankColumns(...input)).toBeNull(); } else if (Array.isArray(expected)) { expect(convertToPercentileRankColumns(...input)).toEqual( - expected.map(expect.objectContaining) + expected.map((el) => (el === null ? null : expect.objectContaining(el))) ); } else { expect(convertToPercentileRankColumns(...input)).toEqual(expect.objectContaining(expected)); diff --git a/src/setup_node_env/harden/index.js b/src/setup_node_env/harden/index.js index 7c3c1528ec2d9..170821a7da21e 100644 --- a/src/setup_node_env/harden/index.js +++ b/src/setup_node_env/harden/index.js @@ -9,6 +9,7 @@ var ritm = require('require-in-the-middle'); var lodashPatch = require('./lodash_template'); var patchChildProcess = require('./child_process'); +var hardenPrototypes = require('./prototype'); // the performance cost of using require-in-the-middle is atm directly related to the number of // registered hooks (as require is patched once for EACH hook) @@ -39,3 +40,9 @@ new ritm.Hook( return module; } ); + +// Use of the `KBN_UNSAFE_DISABLE_PROTOTYPE_HARDENING` environment variable is discouraged, and should only be set to facilitate testing +// specific scenarios. This should never be set in production. +if (!process.env.KBN_UNSAFE_DISABLE_PROTOTYPE_HARDENING) { + hardenPrototypes(); +} diff --git a/src/setup_node_env/harden/prototype.js b/src/setup_node_env/harden/prototype.js new file mode 100644 index 0000000000000..664697af7a34b --- /dev/null +++ b/src/setup_node_env/harden/prototype.js @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +function hardenPrototypes() { + // @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal + // > The Object.seal() static method seals an object. + // > Sealing an object prevents extensions and makes existing properties non-configurable. + // > A sealed object has a fixed set of properties: new properties cannot be added, existing properties cannot be removed, + // > their enumerability and configurability cannot be changed, and its prototype cannot be re-assigned. + // > Values of existing properties can still be changed as long as they are writable. + // Object.freeze would take this one step further, and prevent the values of the properties from being changed as well. + // This is not currently feasible for Kibana, as this functionality is required for some of the libraries that we use, such as react-dom/server. + // While Object.seal() is not a silver bullet, it does provide a good balance between security and compatibility. + // The goal is to prevent a majority of prototype pollution vulnerabilities that can be exploited by an attacker. + + Object.seal(Object.prototype); + Object.seal(Number.prototype); + Object.seal(String.prototype); + Object.seal(Function.prototype); + + // corejs currently manipulates Array.prototype, so we cannot seal it. +} + +module.exports = hardenPrototypes; diff --git a/test/harden/child_process.js b/test/harden/child_process.js index 029b1a038fcbf..2f33a1b0172dc 100644 --- a/test/harden/child_process.js +++ b/test/harden/child_process.js @@ -6,6 +6,9 @@ * Side Public License, v 1. */ +// We must disable prototype hardening to test the pollution +process.env.KBN_UNSAFE_DISABLE_PROTOTYPE_HARDENING = 'true'; + require('../../src/setup_node_env'); const cp = require('child_process'); diff --git a/test/harden/lodash_template.js b/test/harden/lodash_template.js index 49cf7351972e8..c6dc240ffbb63 100644 --- a/test/harden/lodash_template.js +++ b/test/harden/lodash_template.js @@ -6,6 +6,9 @@ * Side Public License, v 1. */ +// We must disable prototype hardening to test the pollution +process.env.KBN_UNSAFE_DISABLE_PROTOTYPE_HARDENING = 'true'; + require('../../src/setup_node_env'); const _ = require('lodash'); // eslint-disable-next-line no-restricted-modules diff --git a/test/harden/prototype.js b/test/harden/prototype.js new file mode 100644 index 0000000000000..dcd5386a17af3 --- /dev/null +++ b/test/harden/prototype.js @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +require('../../src/setup_node_env'); + +const test = require('tape'); + +test('Object.prototype', (t) => { + t.test('Prevents new properties from being added to the prototype', (t) => { + Object.prototype.test = 'whoops'; // eslint-disable-line no-extend-native + t.equal({}.test, undefined); + t.end(); + }); + + t.test('Permits overriding Object.prototype.toString', (t) => { + let originalToString; + t.test('setup', (t) => { + originalToString = Object.prototype.toString; + t.end(); + }); + + t.test('test', (t) => { + // Assert native toString behavior + t.equal({}.toString(), '[object Object]'); + + const { + writable: originalWritable, + enumerable: originalEnumerable, + configurable: originalConfigurable, + } = Object.getOwnPropertyDescriptor(Object.prototype, 'toString'); + + // eslint-disable-next-line no-extend-native + Object.prototype.toString = function toString() { + return 'my new toString function'; + }; + t.equal({}.toString(), 'my new toString function'); + + const toStringDescriptor = Object.getOwnPropertyDescriptor(Object.prototype, 'toString'); + + // Overwriting a property should not change its descriptor. + t.equal(toStringDescriptor.writable, originalWritable); + t.equal(toStringDescriptor.enumerable, originalEnumerable); + t.equal(toStringDescriptor.configurable, originalConfigurable); + + t.end(); + }); + + t.test('teardown', (t) => { + // eslint-disable-next-line no-extend-native + Object.prototype.toString = originalToString; + t.end(); + }); + }); +}); + +test('Number.prototype', (t) => { + t.test('Prevents new properties from being added to the prototype', (t) => { + Number.prototype.test = 'whoops'; // eslint-disable-line no-extend-native + t.equal((12).test, undefined); + t.end(); + }); + + t.test('Permits overriding Number.prototype.toString', (t) => { + let originalToString; + t.test('setup', (t) => { + originalToString = Number.prototype.toString; + t.end(); + }); + + t.test('test', (t) => { + // Assert native toString behavior + t.equal((1).toString(), '1'); + + const { + writable: originalWritable, + enumerable: originalEnumerable, + configurable: originalConfigurable, + } = Object.getOwnPropertyDescriptor(Number.prototype, 'toString'); + + // eslint-disable-next-line no-extend-native + Number.prototype.toString = function toString() { + return 'my new Number.toString function'; + }; + t.equal((12).toString(), 'my new Number.toString function'); + + const toStringDescriptor = Object.getOwnPropertyDescriptor(Number.prototype, 'toString'); + + // Overwriting a property should not change its descriptor. + t.equal(toStringDescriptor.writable, originalWritable); + t.equal(toStringDescriptor.enumerable, originalEnumerable); + t.equal(toStringDescriptor.configurable, originalConfigurable); + + t.end(); + }); + + t.test('teardown', (t) => { + // eslint-disable-next-line no-extend-native + Number.prototype.toString = originalToString; + t.end(); + }); + }); +}); + +test('String.prototype', (t) => { + t.test('Prevents new properties from being added to the prototype', (t) => { + String.prototype.test = 'whoops'; // eslint-disable-line no-extend-native + t.equal('hello'.test, undefined); + t.end(); + }); + + t.test('Permits overriding String.prototype.toString', (t) => { + let originalToString; + t.test('setup', (t) => { + originalToString = String.prototype.toString; + t.end(); + }); + + t.test('test', (t) => { + // Assert native toString behavior + t.equal((1).toString(), '1'); + + const { + writable: originalWritable, + enumerable: originalEnumerable, + configurable: originalConfigurable, + } = Object.getOwnPropertyDescriptor(String.prototype, 'toString'); + + // eslint-disable-next-line no-extend-native + String.prototype.toString = function toString() { + return 'my new String.toString function'; + }; + t.equal('test'.toString(), 'my new String.toString function'); + + const toStringDescriptor = Object.getOwnPropertyDescriptor(String.prototype, 'toString'); + + // Overwriting a property should not change its descriptor. + t.equal(toStringDescriptor.writable, originalWritable); + t.equal(toStringDescriptor.enumerable, originalEnumerable); + t.equal(toStringDescriptor.configurable, originalConfigurable); + + t.end(); + }); + + t.test('teardown', (t) => { + // eslint-disable-next-line no-extend-native + String.prototype.toString = originalToString; + t.end(); + }); + }); +}); + +test('Function.prototype', (t) => { + t.test('Prevents new properties from being added to the prototype', (t) => { + Function.prototype.test = 'whoops'; // eslint-disable-line no-extend-native + const fn = function testFn() {}; + t.equal(fn.test, undefined); + t.end(); + }); + + t.test('Permits overriding Function.prototype.toString', (t) => { + let originalToString; + t.test('setup', (t) => { + originalToString = Function.prototype.toString; + t.end(); + }); + + t.test('test', (t) => { + // Assert native toString behavior + const fn = function testFn() {}; + t.equal(fn.toString(), 'function testFn() {}'); + + const { + writable: originalWritable, + enumerable: originalEnumerable, + configurable: originalConfigurable, + } = Object.getOwnPropertyDescriptor(Function.prototype, 'toString'); + + // eslint-disable-next-line no-extend-native + Function.prototype.toString = function toString() { + return 'my new Function.toString function'; + }; + t.equal(fn.toString(), 'my new Function.toString function'); + + const toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, 'toString'); + + // Overwriting a property should not change its descriptor. + t.equal(toStringDescriptor.writable, originalWritable); + t.equal(toStringDescriptor.enumerable, originalEnumerable); + t.equal(toStringDescriptor.configurable, originalConfigurable); + + t.end(); + }); + + t.test('teardown', (t) => { + // eslint-disable-next-line no-extend-native + Function.prototype.toString = originalToString; + t.end(); + }); + }); +}); diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index 7b967b8591bf4..3f59cc4ff3ac5 100644 --- a/test/plugin_functional/config.ts +++ b/test/plugin_functional/config.ts @@ -19,6 +19,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./test_suites/telemetry'), require.resolve('./test_suites/core'), require.resolve('./test_suites/custom_visualizations'), + require.resolve('./test_suites/hardening'), require.resolve('./test_suites/panel_actions'), require.resolve('./test_suites/core_plugins'), require.resolve('./test_suites/management'), diff --git a/test/plugin_functional/plugins/hardening/kibana.jsonc b/test/plugin_functional/plugins/hardening/kibana.jsonc new file mode 100644 index 0000000000000..c5680af0dd35e --- /dev/null +++ b/test/plugin_functional/plugins/hardening/kibana.jsonc @@ -0,0 +1,13 @@ +{ + "type": "plugin", + "id": "@kbn/hardening-plugin", + "owner": "@elastic/kibana-security", + "plugin": { + "id": "hardeningPlugin", + "server": true, + "browser": false, + "configPath": [ + "hardening_plugin" + ] + } +} diff --git a/test/plugin_functional/plugins/hardening/package.json b/test/plugin_functional/plugins/hardening/package.json new file mode 100644 index 0000000000000..c33feeb7a4f52 --- /dev/null +++ b/test/plugin_functional/plugins/hardening/package.json @@ -0,0 +1,14 @@ +{ + "name": "@kbn/hardening-plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/hardening", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "SSPL-1.0 OR Elastic License 2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" + } +} \ No newline at end of file diff --git a/test/plugin_functional/plugins/hardening/server/index.ts b/test/plugin_functional/plugins/hardening/server/index.ts new file mode 100644 index 0000000000000..cafadea796f5e --- /dev/null +++ b/test/plugin_functional/plugins/hardening/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HardeningPlugin } from './plugin'; + +export const plugin = async () => new HardeningPlugin(); diff --git a/test/plugin_functional/plugins/hardening/server/plugin.ts b/test/plugin_functional/plugins/hardening/server/plugin.ts new file mode 100644 index 0000000000000..451f4e233169f --- /dev/null +++ b/test/plugin_functional/plugins/hardening/server/plugin.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Plugin, CoreSetup } from '@kbn/core/server'; + +export class HardeningPlugin implements Plugin { + public setup(core: CoreSetup, deps: {}) { + core.http.createRouter().get( + { + path: '/api/hardening/_pollute_prototypes', + validate: false, + }, + async (context, request, response) => { + const result: Record; error?: string }> = { + object: {}, + number: {}, + string: {}, + fn: {}, + array: {}, + }; + // Attempt to pollute Object.prototype + try { + (({}) as any).__proto__.polluted = true; + } catch (e) { + result.object.error = e.message; + } finally { + result.object.prototype = { ...Object.keys(Object.getPrototypeOf({})) }; + } + + // Attempt to pollute String.prototype + try { + ('asdf' as any).__proto__.polluted = true; + } catch (e) { + result.string.error = e.message; + } finally { + result.string.prototype = { ...Object.keys(Object.getPrototypeOf('asf')) }; + } + + // Attempt to pollute Number.prototype + try { + (12 as any).__proto__.polluted = true; + } catch (e) { + result.number.error = e.message; + } finally { + result.number.prototype = { ...Object.keys(Object.getPrototypeOf(12)) }; + } + + // Attempt to pollute Function.prototype + const fn = function fn() {}; + try { + (fn as any).__proto__.polluted = true; + } catch (e) { + result.fn.error = e.message; + } finally { + result.fn.prototype = { ...Object.keys(Object.getPrototypeOf(fn)) }; + } + + // Attempt to pollute Array.prototype + try { + ([] as any).__proto__.polluted = true; + } catch (e) { + result.array.error = e.message; + } finally { + result.array.prototype = { ...Object.keys(Object.getPrototypeOf([])) }; + } + + return response.ok({ body: result }); + } + ); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/hardening/tsconfig.json b/test/plugin_functional/plugins/hardening/tsconfig.json new file mode 100644 index 0000000000000..bf146797a42ee --- /dev/null +++ b/test/plugin_functional/plugins/hardening/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/core" + ] +} diff --git a/test/plugin_functional/snapshots/baseline/hardening/prototype.json b/test/plugin_functional/snapshots/baseline/hardening/prototype.json new file mode 100644 index 0000000000000..4e3a9d8219492 --- /dev/null +++ b/test/plugin_functional/snapshots/baseline/hardening/prototype.json @@ -0,0 +1 @@ +{"object":{"error":"Cannot add property polluted, object is not extensible","prototype":{}},"number":{"error":"Cannot add property polluted, object is not extensible","prototype":{}},"string":{"error":"Cannot add property polluted, object is not extensible","prototype":{}},"fn":{"error":"Cannot add property polluted, object is not extensible","prototype":{}},"array":{"prototype":{"0":"polluted"}}} \ No newline at end of file diff --git a/test/plugin_functional/test_suites/hardening/index.ts b/test/plugin_functional/test_suites/hardening/index.ts new file mode 100644 index 0000000000000..b4eedb16495fe --- /dev/null +++ b/test/plugin_functional/test_suites/hardening/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ loadTestFile }: PluginFunctionalProviderContext) { + describe('hardening', function () { + loadTestFile(require.resolve('./prototype')); + }); +} diff --git a/test/plugin_functional/test_suites/hardening/prototype.ts b/test/plugin_functional/test_suites/hardening/prototype.ts new file mode 100644 index 0000000000000..823667a009544 --- /dev/null +++ b/test/plugin_functional/test_suites/hardening/prototype.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ getService }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + const snapshots = getService('snapshots'); + + describe('prototype', function () { + it('does not allow polluting most prototypes', async () => { + const response = await supertest + .get('/api/hardening/_pollute_prototypes') + .set('kbn-xsrf', 'true') + .expect(200); + + await snapshots.compareAgainstBaseline('hardening/prototype', response.body); + }); + }); +} diff --git a/tsconfig.base.json b/tsconfig.base.json index c80b6419fa026..b8a8baf855e88 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -980,6 +980,8 @@ "@kbn/handlebars/*": ["packages/kbn-handlebars/*"], "@kbn/hapi-mocks": ["packages/kbn-hapi-mocks"], "@kbn/hapi-mocks/*": ["packages/kbn-hapi-mocks/*"], + "@kbn/hardening-plugin": ["test/plugin_functional/plugins/hardening"], + "@kbn/hardening-plugin/*": ["test/plugin_functional/plugins/hardening/*"], "@kbn/health-gateway-server": ["packages/kbn-health-gateway-server"], "@kbn/health-gateway-server/*": ["packages/kbn-health-gateway-server/*"], "@kbn/hello-world-plugin": ["examples/hello_world"], diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx deleted file mode 100644 index 7fd0a3f3b133d..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EcsFlatTyped } from '../../constants'; -import { getUnallowedValueRequestItems, getValidValues, hasAllowedValues } from './helpers'; - -describe('helpers', () => { - describe('hasAllowedValues', () => { - test('it returns true for a field that has `allowed_values`', () => { - expect( - hasAllowedValues({ - ecsMetadata: EcsFlatTyped, - fieldName: 'event.category', - }) - ).toBe(true); - }); - - test('it returns false for a field that does NOT have `allowed_values`', () => { - expect( - hasAllowedValues({ - ecsMetadata: EcsFlatTyped, - fieldName: 'host.name', - }) - ).toBe(false); - }); - - test('it returns false for a field that does NOT exist in `ecsMetadata`', () => { - expect( - hasAllowedValues({ - ecsMetadata: EcsFlatTyped, - fieldName: 'does.NOT.exist', - }) - ).toBe(false); - }); - }); - - describe('getValidValues', () => { - test('it returns the expected valid values', () => { - expect(getValidValues(EcsFlatTyped['event.category'])).toEqual( - expect.arrayContaining([ - 'authentication', - 'configuration', - 'database', - 'driver', - 'email', - 'file', - 'host', - 'iam', - 'intrusion_detection', - 'malware', - 'network', - 'package', - 'process', - 'registry', - 'session', - 'threat', - 'vulnerability', - 'web', - ]) - ); - }); - - test('it returns an empty array when the `field` does NOT have `allowed_values`', () => { - expect(getValidValues(EcsFlatTyped['host.name'])).toEqual([]); - }); - - test('it returns an empty array when `field` is undefined', () => { - expect(getValidValues(undefined)).toEqual([]); - }); - }); - - describe('getUnallowedValueRequestItems', () => { - test('it returns the expected request items', () => { - expect( - getUnallowedValueRequestItems({ - ecsMetadata: EcsFlatTyped, - indexName: 'auditbeat-*', - }) - ).toEqual([ - { - indexName: 'auditbeat-*', - indexFieldName: 'event.category', - allowedValues: expect.arrayContaining([ - 'authentication', - 'configuration', - 'database', - 'driver', - 'email', - 'file', - 'host', - 'iam', - 'intrusion_detection', - 'malware', - 'network', - 'package', - 'process', - 'registry', - 'session', - 'threat', - 'vulnerability', - 'web', - ]), - }, - { - indexName: 'auditbeat-*', - indexFieldName: 'event.kind', - allowedValues: expect.arrayContaining([ - 'alert', - 'enrichment', - 'event', - 'metric', - 'state', - 'pipeline_error', - 'signal', - ]), - }, - { - indexName: 'auditbeat-*', - indexFieldName: 'event.outcome', - allowedValues: expect.arrayContaining(['failure', 'success', 'unknown']), - }, - { - indexName: 'auditbeat-*', - indexFieldName: 'event.type', - allowedValues: expect.arrayContaining([ - 'access', - 'admin', - 'allowed', - 'change', - 'connection', - 'creation', - 'deletion', - 'denied', - 'end', - 'error', - 'group', - 'indicator', - 'info', - 'installation', - 'protocol', - 'start', - 'user', - ]), - }, - ]); - }); - }); -}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx deleted file mode 100644 index 595c17e16faaa..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { render, screen, waitFor } from '@testing-library/react'; -import React from 'react'; - -import { - TestDataQualityProviders, - TestExternalProviders, -} from '../../mock/test_providers/test_providers'; -import { Body } from '.'; - -describe('IndexInvalidValues', () => { - test('it renders the data quality summary', async () => { - render( - - - - - - ); - - await waitFor(() => { - expect(screen.getByTestId('dataQualitySummary')).toBeInTheDocument(); - }); - }); - - describe('patterns', () => { - const patterns = ['.alerts-security.alerts-default', 'auditbeat-*', 'logs-*', 'packetbeat-*']; - - test(`it renders the '${patterns.join(', ')}' patterns`, async () => { - render( - - - - - - ); - - for (const pattern of patterns) { - await waitFor(() => { - expect(screen.getByTestId(`${pattern}PatternPanel`)).toBeInTheDocument(); - }); - } - }); - }); -}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.tsx deleted file mode 100644 index a8ceecdc76c09..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import React from 'react'; - -import { DataQualityDetails } from './data_quality_details'; -import { DataQualitySummary } from '../data_quality_summary'; - -const BodyComponent: React.FC = () => { - return ( - - - - - - - - - - - ); -}; - -export const Body = React.memo(BodyComponent); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts deleted file mode 100644 index 0058e2436a526..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts +++ /dev/null @@ -1,1537 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types'; -import { omit } from 'lodash/fp'; - -import { - FieldType, - getDocsCount, - getErrorSummary, - getErrorSummaries, - getErrorSummariesForRollup, - getEnrichedFieldMetadata, - getFieldTypes, - getPatternIlmPhaseDescription, - getIlmPhaseDescription, - getIndexNames, - getIsInSameFamily, - getMissingTimestampFieldMetadata, - getPartitionedFieldMetadata, - getPartitionedFieldMetadataStats, - getSizeInBytes, - getTotalDocsCount, - getTotalPatternIncompatible, - getTotalPatternIndicesChecked, - getTotalPatternSameFamily, - getTotalSizeInBytes, - isMappingCompatible, - postStorageResult, - getStorageResults, - StorageResult, - formatStorageResult, -} from './helpers'; -import { - hostNameWithTextMapping, - hostNameKeyword, - someField, - someFieldKeyword, - sourceIpWithTextMapping, - sourceIpKeyword, - sourcePort, - timestamp, - eventCategoryWithUnallowedValues, -} from './mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { mockIlmExplain } from './mock/ilm_explain/mock_ilm_explain'; -import { mockMappingsProperties } from './mock/mappings_properties/mock_mappings_properties'; -import { alertIndexNoResults } from './mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { - packetbeatNoResults, - packetbeatWithSomeErrors, -} from './mock/pattern_rollup/mock_packetbeat_pattern_rollup'; -import { - auditbeatNoResults, - auditbeatWithAllResults, -} from './mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { mockStats } from './mock/stats/mock_stats'; -import { mockStatsAuditbeatIndex } from './mock/stats/mock_stats_packetbeat_index'; -import { mockStatsPacketbeatIndex } from './mock/stats/mock_stats_auditbeat_index'; -import { - COLD_DESCRIPTION, - FROZEN_DESCRIPTION, - HOT_DESCRIPTION, - UNMANAGED_DESCRIPTION, - WARM_DESCRIPTION, -} from './translations'; -import { - DataQualityCheckResult, - EnrichedFieldMetadata, - PartitionedFieldMetadata, - PatternRollup, - UnallowedValueCount, -} from './types'; -import { httpServiceMock } from '@kbn/core-http-browser-mocks'; -import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; -import { EcsFlatTyped } from './constants'; -import { mockPartitionedFieldMetadataWithSameFamily } from './mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family'; - -describe('helpers', () => { - describe('getTotalPatternSameFamily', () => { - const baseResult: DataQualityCheckResult = { - docsCount: 4, - error: null, - ilmPhase: 'unmanaged', - incompatible: 3, - indexName: 'auditbeat-custom-index-1', - markdownComments: [ - '### auditbeat-custom-index-1\n', - '| Result | Index | Docs | Incompatible fields | ILM Phase |\n|--------|-------|------|---------------------|-----------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` |\n\n', - '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', - "#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", - '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2),\n`theory` (1) |\n\n', - ], - pattern: 'auditbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }; - - it('returns undefined when results is undefined', () => { - expect(getTotalPatternSameFamily(undefined)).toBeUndefined(); - }); - - it('returns 0 when results is an empty object', () => { - expect(getTotalPatternSameFamily({})).toBe(0); - }); - - it('should sum sameFamily values and return the total', () => { - const results: Record = { - a: { - ...baseResult, - indexName: 'a', - markdownComments: [], - pattern: 'pattern', - sameFamily: 2, - }, - b: { - ...baseResult, - indexName: 'b', - markdownComments: [], - pattern: 'pattern', - sameFamily: 3, - }, - c: { ...baseResult, indexName: 'c', markdownComments: [], pattern: 'pattern' }, - }; - - expect(getTotalPatternSameFamily(results)).toBe(5); - }); - - it('handles a mix of defined and undefined sameFamily values', () => { - const results: Record = { - a: { - ...baseResult, - indexName: 'a', - markdownComments: [], - pattern: 'pattern', - sameFamily: 1, - }, - b: { - ...baseResult, - indexName: 'b', - markdownComments: [], - pattern: 'pattern', - sameFamily: undefined, - }, - c: { - ...baseResult, - indexName: 'c', - markdownComments: [], - pattern: 'pattern', - sameFamily: 2, - }, - }; - - expect(getTotalPatternSameFamily(results)).toBe(3); - }); - }); - - describe('getIndexNames', () => { - const isILMAvailable = true; - const ilmPhases = ['hot', 'warm', 'unmanaged']; - - test('returns the expected index names when they have an ILM phase included in the ilmPhases list', () => { - expect( - getIndexNames({ - ilmExplain: mockIlmExplain, // <-- the mock indexes have 'hot' ILM phases - ilmPhases, - isILMAvailable, - stats: mockStats, - }) - ).toEqual([ - '.ds-packetbeat-8.6.1-2023.02.04-000001', - '.ds-packetbeat-8.5.3-2023.02.04-000001', - 'auditbeat-custom-index-1', - ]); - }); - - test('returns the expected filtered index names when they do NOT have an ILM phase included in the ilmPhases list', () => { - expect( - getIndexNames({ - ilmExplain: mockIlmExplain, // <-- the mock indexes have 'hot' and 'unmanaged' ILM phases... - ilmPhases: ['warm', 'unmanaged'], // <-- ...but we don't ask for 'hot' - isILMAvailable, - stats: mockStats, - }) - ).toEqual(['auditbeat-custom-index-1']); // <-- the 'unmanaged' index - }); - - test('returns the expected index names when the `ilmExplain` is missing a record for an index', () => { - // the following `ilmExplain` is missing a record for one of the two packetbeat indexes: - const ilmExplainWithMissingIndex: Record = omit( - '.ds-packetbeat-8.6.1-2023.02.04-000001', - mockIlmExplain - ); - - expect( - getIndexNames({ - ilmExplain: ilmExplainWithMissingIndex, // <-- the mock indexes have 'hot' ILM phases... - ilmPhases: ['hot', 'warm', 'unmanaged'], - isILMAvailable, - stats: mockStats, - }) - ).toEqual(['.ds-packetbeat-8.5.3-2023.02.04-000001', 'auditbeat-custom-index-1']); // <-- only includes two of the three indices, because the other one is missing an ILM explain record - }); - - test('returns empty index names when `ilmPhases` is empty', () => { - expect( - getIndexNames({ - ilmExplain: mockIlmExplain, - ilmPhases: [], - isILMAvailable, - stats: mockStats, - }) - ).toEqual([]); - }); - - test('returns empty index names when they have an ILM phase that matches', () => { - expect( - getIndexNames({ - ilmExplain: null, - ilmPhases, - isILMAvailable, - stats: mockStats, - }) - ).toEqual([]); - }); - - test('returns empty index names when just `stats` is null', () => { - expect( - getIndexNames({ - ilmExplain: mockIlmExplain, - ilmPhases, - isILMAvailable, - stats: null, - }) - ).toEqual([]); - }); - - test('returns empty index names when both `ilmExplain` and `stats` are null', () => { - expect( - getIndexNames({ - ilmExplain: null, - ilmPhases, - isILMAvailable, - stats: null, - }) - ).toEqual([]); - }); - }); - - describe('getFieldTypes', () => { - const expected = [ - { - field: '@timestamp', - type: 'date', - }, - { - field: 'event.category', - type: 'keyword', - }, - { - field: 'host.name', - type: 'text', - }, - { - field: 'host.name.keyword', - type: 'keyword', - }, - { - field: 'some.field', - type: 'text', - }, - { - field: 'some.field.keyword', - type: 'keyword', - }, - { - field: 'source.ip', - type: 'text', - }, - { - field: 'source.ip.keyword', - type: 'keyword', - }, - { - field: 'source.port', - type: 'long', - }, - ]; - - test('it flattens the field names and types in the mapping properties', () => { - expect(getFieldTypes(mockMappingsProperties)).toEqual(expected); - }); - - test('it throws a type error when mappingsProperties is not flatten-able', () => { - // @ts-expect-error - const invalidType: Record = []; // <-- this is an array, NOT a valid Record - - expect(() => getFieldTypes(invalidType)).toThrowError('Root value is not flatten-able'); - }); - }); - - describe('getIsInSameFamily', () => { - test('it returns false when ecsExpectedType is undefined', () => { - expect(getIsInSameFamily({ ecsExpectedType: undefined, type: 'keyword' })).toBe(false); - }); - - const expectedFamilyMembers: { - [key: string]: string[]; - } = { - constant_keyword: ['keyword', 'wildcard'], // `keyword` and `wildcard` in the same family as `constant_keyword` - keyword: ['constant_keyword', 'wildcard'], - match_only_text: ['text'], - text: ['match_only_text'], - wildcard: ['keyword', 'constant_keyword'], - }; - - const ecsExpectedTypes = Object.keys(expectedFamilyMembers); - - ecsExpectedTypes.forEach((ecsExpectedType) => { - const otherMembersOfSameFamily = expectedFamilyMembers[ecsExpectedType]; - - otherMembersOfSameFamily.forEach((type) => - test(`it returns true for ecsExpectedType '${ecsExpectedType}' when given '${type}', a type in the same family`, () => { - expect(getIsInSameFamily({ ecsExpectedType, type })).toBe(true); - }) - ); - - test(`it returns false for ecsExpectedType '${ecsExpectedType}' when given 'date', a type NOT in the same family`, () => { - expect(getIsInSameFamily({ ecsExpectedType, type: 'date' })).toBe(false); - }); - }); - }); - - describe('isMappingCompatible', () => { - test('it returns true for an exact match', () => { - expect(isMappingCompatible({ ecsExpectedType: 'keyword', type: 'keyword' })).toBe(true); - }); - - test("it returns false when both types don't exactly match", () => { - expect(isMappingCompatible({ ecsExpectedType: 'wildcard', type: 'keyword' })).toBe(false); - }); - }); - - describe('getEnrichedFieldMetadata', () => { - /** - * The ECS schema - * https://raw.githubusercontent.com/elastic/ecs/main/generated/ecs/ecs_flat.yml - * defines a handful of fields that have `allowed_values`. For these - * fields, the documents in an index should only have specific values. - * - * This instance of the type `Record` - * represents an index that doesn't have any unallowed values, for the - * specified keys in the map, i.e. `event.category`, `event.kind`, etc. - * - * This will be used to test the happy path. Variants of this - * value will be used to test unhappy paths. - */ - const noUnallowedValues: Record = { - 'event.category': [], - 'event.kind': [], - 'event.outcome': [], - 'event.type': [], - }; - - /** - * Represents an index that has unallowed values, for the - * `event.category` field. The other fields in the collection, - * i.e. `event.kind`, don't have any unallowed values. - * - * This instance will be used to test paths where a field is - * NOT ECS complaint, because the index has unallowed values. - */ - const unallowedValues: Record = { - 'event.category': [ - { - count: 2, - fieldName: 'an_invalid_category', - }, - { - count: 1, - fieldName: 'theory', - }, - ], - 'event.kind': [], - 'event.outcome': [], - 'event.type': [], - }; - - /** - * This instance of a `FieldType` has the correct mapping for the - * `event.category` field. - * - * This instance will be used to test paths where the index has - * a valid mapping for the `event.category` field. - */ - const fieldMetadataCorrectMappingType: FieldType = { - field: 'event.category', - type: 'keyword', // <-- this index has the correct mapping type - }; - - /** - * This `EnrichedFieldMetadata` for the `event.category` field, - * represents a happy path result, where the index being checked: - * - * 1) The `type` of the field in the index, `keyword`, matches the expected - * `type` of the `event.category` field, as defined by the `EcsMetadata` - * 2) The index doesn't have any unallowed values for the `event.category` field - * - * Since this is a happy path result, it has the following values: - * `indexInvalidValues` is an empty array, because the index does not contain any invalid values - * `isEcsCompliant` is true, because the index has the expected mapping type, and no unallowed values - */ - const happyPathResultSample: EnrichedFieldMetadata = { - dashed_name: 'event-category', - description: - 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', - example: 'authentication', - flat_name: 'event.category', - ignore_above: 1024, - level: 'core', - name: 'category', - normalize: ['array'], - short: 'Event category. The second categorization field in the hierarchy.', - type: 'keyword', - indexFieldName: 'event.category', - indexFieldType: 'keyword', // a valid mapping, because the `type` property from the `ecsMetadata` is also `keyword` - indexInvalidValues: [], // empty array, because the index does not contain any invalid values - hasEcsMetadata: true, - isEcsCompliant: true, // because the index has the expected mapping type, and no unallowed values - isInSameFamily: false, - }; - - /** - * Creates expected result matcher based on the happy path result sample. Please, add similar `expect` based assertions to it if anything breaks - * with an ECS upgrade, instead of hardcoding the values. - */ - const expectedResult = (extraFields: Record = {}) => - expect.objectContaining({ - ...happyPathResultSample, - ...extraFields, - allowed_values: expect.arrayContaining([ - expect.objectContaining({ - description: expect.any(String), - name: expect.any(String), - expected_event_types: expect.any(Array), - }), - ]), - }); - - test('it returns the happy path result when the index has no mapping conflicts, and no unallowed values', () => { - expect( - getEnrichedFieldMetadata({ - ecsMetadata: EcsFlatTyped, - fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index - unallowedValues: noUnallowedValues, // no unallowed values for `event.category` in this index - }) - ).toEqual(expectedResult()); - }); - - test('it returns the happy path result when the index has no mapping conflicts, and the unallowedValues map does not contain an entry for the field', () => { - // create an `unallowedValues` that doesn't have an entry for `event.category`: - const noEntryForEventCategory: Record = omit( - 'event.category', - unallowedValues - ); - - expect( - getEnrichedFieldMetadata({ - ecsMetadata: EcsFlatTyped, - fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index - unallowedValues: noEntryForEventCategory, // a lookup in this map for the `event.category` field will return undefined - }) - ).toEqual(expectedResult()); - }); - - test('it returns a result with the expected `indexInvalidValues` and `isEcsCompliant` when the index has no mapping conflict, but it has unallowed values', () => { - expect( - getEnrichedFieldMetadata({ - ecsMetadata: EcsFlatTyped, - fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index - unallowedValues, // this index has unallowed values for the event.category field - }) - ).toEqual( - expectedResult({ - indexInvalidValues: [ - { - count: 2, - fieldName: 'an_invalid_category', - }, - { - count: 1, - fieldName: 'theory', - }, - ], - isEcsCompliant: false, // because there are unallowed values - }) - ); - }); - - test('it returns a result with the expected `isEcsCompliant` and `isInSameFamily` when the index type does not match ECS, but NO unallowed values', () => { - const indexFieldType = 'text'; - - expect( - getEnrichedFieldMetadata({ - ecsMetadata: EcsFlatTyped, - fieldMetadata: { - field: 'event.category', // `event.category` is a `keyword`, per the ECS spec - type: indexFieldType, // this index has a mapping of `text` instead - }, - unallowedValues: noUnallowedValues, // no unallowed values for `event.category` in this index - }) - ).toEqual( - expectedResult({ - indexFieldType, - isEcsCompliant: false, // `keyword` !== `text` - isInSameFamily: false, // `keyword` and `text` are not in the same family - }) - ); - }); - - test('it returns a result with the expected `isEcsCompliant` and `isInSameFamily` when the mapping is is in the same family', () => { - const indexFieldType = 'wildcard'; - - expect( - getEnrichedFieldMetadata({ - ecsMetadata: EcsFlatTyped, - fieldMetadata: { - field: 'event.category', // `event.category` is a `keyword` per the ECS spec - type: indexFieldType, // this index has a mapping of `wildcard` instead - }, - unallowedValues: noUnallowedValues, // no unallowed values for `event.category` in this index - }) - ).toEqual( - expectedResult({ - indexFieldType, - isEcsCompliant: false, // `wildcard` !== `keyword` - isInSameFamily: true, // `wildcard` and `keyword` are in the same family - }) - ); - }); - - test('it returns a result with the expected `indexInvalidValues`,`isEcsCompliant`, and `isInSameFamily` when the index has BOTH mapping conflicts, and unallowed values', () => { - const indexFieldType = 'text'; - - expect( - getEnrichedFieldMetadata({ - ecsMetadata: EcsFlatTyped, - fieldMetadata: { - field: 'event.category', // `event.category` is a `keyword` per the ECS spec - type: indexFieldType, // this index has a mapping of `text` instead - }, - unallowedValues, // this index also has unallowed values for the event.category field - }) - ).toEqual( - expectedResult({ - indexFieldType, - indexInvalidValues: [ - { - count: 2, - fieldName: 'an_invalid_category', - }, - { - count: 1, - fieldName: 'theory', - }, - ], - isEcsCompliant: false, // because there are BOTH mapping conflicts and unallowed values - isInSameFamily: false, // `text` and `keyword` are not in the same family - }) - ); - }); - - test('it returns the expected result for a custom field, i.e. a field that does NOT have an entry in `ecsMetadata`', () => { - const field = 'a_custom_field'; // not defined by ECS - const indexFieldType = 'keyword'; - - expect( - getEnrichedFieldMetadata({ - ecsMetadata: EcsFlatTyped, - fieldMetadata: { - field, - type: indexFieldType, // no mapping conflict, because ECS doesn't define this field - }, - unallowedValues: noUnallowedValues, // no unallowed values for `a_custom_field` in this index - }) - ).toEqual({ - indexFieldName: field, - indexFieldType, - indexInvalidValues: [], - hasEcsMetadata: false, - isEcsCompliant: false, - isInSameFamily: false, // custom fields are never in the same family - }); - }); - }); - - describe('getMissingTimestampFieldMetadata', () => { - test('it returns the expected `EnrichedFieldMetadata`', () => { - expect(getMissingTimestampFieldMetadata()).toEqual({ - ...EcsFlatTyped['@timestamp'], - hasEcsMetadata: true, - indexFieldName: '@timestamp', - indexFieldType: '-', // the index did NOT define a mapping for @timestamp - indexInvalidValues: [], - isEcsCompliant: false, // an index must define the @timestamp mapping - isInSameFamily: false, // `date` is not a member of any families - }); - }); - }); - - describe('getPartitionedFieldMetadata', () => { - test('it places all the `EnrichedFieldMetadata` in the expected categories', () => { - const enrichedFieldMetadata: EnrichedFieldMetadata[] = [ - timestamp, - eventCategoryWithUnallowedValues, - hostNameWithTextMapping, - hostNameKeyword, - someField, - someFieldKeyword, - sourceIpWithTextMapping, - sourceIpKeyword, - sourcePort, - ]; - const expected: PartitionedFieldMetadata = { - all: [ - timestamp, - eventCategoryWithUnallowedValues, - hostNameWithTextMapping, - hostNameKeyword, - someField, - someFieldKeyword, - sourceIpWithTextMapping, - sourceIpKeyword, - sourcePort, - ], - ecsCompliant: [timestamp, sourcePort], - custom: [hostNameKeyword, someField, someFieldKeyword, sourceIpKeyword], - incompatible: [ - eventCategoryWithUnallowedValues, - hostNameWithTextMapping, - sourceIpWithTextMapping, - ], - sameFamily: [], - }; - - expect(getPartitionedFieldMetadata(enrichedFieldMetadata)).toEqual(expected); - }); - }); - - describe('getPartitionedFieldMetadataStats', () => { - test('it returns the expected stats', () => { - const partitionedFieldMetadata: PartitionedFieldMetadata = { - all: [ - timestamp, - eventCategoryWithUnallowedValues, - hostNameWithTextMapping, - hostNameKeyword, - someField, - someFieldKeyword, - sourceIpWithTextMapping, - sourceIpKeyword, - sourcePort, - ], - ecsCompliant: [timestamp, sourcePort], - custom: [hostNameKeyword, someField, someFieldKeyword, sourceIpKeyword], - incompatible: [ - eventCategoryWithUnallowedValues, - hostNameWithTextMapping, - sourceIpWithTextMapping, - ], - sameFamily: [], - }; - - expect(getPartitionedFieldMetadataStats(partitionedFieldMetadata)).toEqual({ - all: 9, - custom: 4, - ecsCompliant: 2, - incompatible: 3, - sameFamily: 0, - }); - }); - }); - - describe('getDocsCount', () => { - test('it returns the expected docs count when `stats` contains the `indexName`', () => { - const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; - const expectedCount = mockStatsPacketbeatIndex[indexName].num_docs; - - expect( - getDocsCount({ - indexName, - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(expectedCount); - }); - - test('it returns zero when `stats` does NOT contain the `indexName`', () => { - const indexName = 'not-gonna-find-it'; - - expect( - getDocsCount({ - indexName, - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(0); - }); - - test('it returns zero when `stats` is null', () => { - const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; - - expect( - getDocsCount({ - indexName, - stats: null, - }) - ).toEqual(0); - }); - - test('it returns the expected total for a green index, where `primaries.docs.count` and `total.docs.count` have different values', () => { - const indexName = 'auditbeat-custom-index-1'; - - expect( - getDocsCount({ - indexName, - stats: mockStatsAuditbeatIndex, - }) - ).toEqual(mockStatsAuditbeatIndex[indexName].num_docs); - }); - }); - - describe('getSizeInBytes', () => { - test('it returns the expected size when `stats` contains the `indexName`', () => { - const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; - const expectedCount = mockStatsPacketbeatIndex[indexName].size_in_bytes; - - expect( - getSizeInBytes({ - indexName, - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(expectedCount); - }); - - test('it returns undefined when `stats` does NOT contain the `indexName`', () => { - const indexName = 'not-gonna-find-it'; - - expect( - getSizeInBytes({ - indexName, - stats: mockStatsPacketbeatIndex, - }) - ).toBeUndefined(); - }); - - test('it returns undefined when `stats` is null', () => { - const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; - - expect( - getSizeInBytes({ - indexName, - stats: null, - }) - ).toBeUndefined(); - }); - - test('it returns the expected size for a green index, where `primaries.store.size_in_bytes` and `total.store.size_in_bytes` have different values', () => { - const indexName = 'auditbeat-custom-index-1'; - - expect( - getSizeInBytes({ - indexName, - stats: mockStatsAuditbeatIndex, - }) - ).toEqual(mockStatsAuditbeatIndex[indexName].size_in_bytes); - }); - }); - - describe('getTotalDocsCount', () => { - test('it returns the expected total given a subset of index names in the stats', () => { - const indexName = '.ds-packetbeat-8.5.3-2023.02.04-000001'; - const expectedCount = mockStatsPacketbeatIndex[indexName].num_docs; - - expect( - getTotalDocsCount({ - indexNames: [indexName], - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(expectedCount); - }); - - test('it returns the expected total given all index names in the stats', () => { - const allIndexNamesInStats = [ - '.ds-packetbeat-8.6.1-2023.02.04-000001', - '.ds-packetbeat-8.5.3-2023.02.04-000001', - ]; - - expect( - getTotalDocsCount({ - indexNames: allIndexNamesInStats, - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(3258632); - }); - - test('it returns zero given an empty collection of index names', () => { - expect( - getTotalDocsCount({ - indexNames: [], // <-- empty - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(0); - }); - - test('it returns the expected total for a green index', () => { - const indexName = 'auditbeat-custom-index-1'; - const expectedCount = mockStatsAuditbeatIndex[indexName].num_docs; - - expect( - getTotalDocsCount({ - indexNames: [indexName], - stats: mockStatsAuditbeatIndex, - }) - ).toEqual(expectedCount); - }); - }); - - describe('getTotalSizeInBytes', () => { - test('it returns the expected total given a subset of index names in the stats', () => { - const indexName = '.ds-packetbeat-8.5.3-2023.02.04-000001'; - const expectedCount = mockStatsPacketbeatIndex[indexName].size_in_bytes; - - expect( - getTotalSizeInBytes({ - indexNames: [indexName], - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(expectedCount); - }); - - test('it returns the expected total given all index names in the stats', () => { - const allIndexNamesInStats = [ - '.ds-packetbeat-8.6.1-2023.02.04-000001', - '.ds-packetbeat-8.5.3-2023.02.04-000001', - ]; - - expect( - getTotalSizeInBytes({ - indexNames: allIndexNamesInStats, - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(1464758182); - }); - - test('it returns undefined given an empty collection of index names', () => { - expect( - getTotalSizeInBytes({ - indexNames: [], // <-- empty - stats: mockStatsPacketbeatIndex, - }) - ).toBeUndefined(); - }); - - test('it returns undefined if sizeInByte in not an integer', () => { - const indexName = 'auditbeat-custom-index-1'; - - expect( - getTotalSizeInBytes({ - indexNames: [indexName], - stats: { [indexName]: { ...mockStatsAuditbeatIndex[indexName], size_in_bytes: null } }, - }) - ).toBeUndefined(); - }); - - test('it returns the expected total for an index', () => { - const indexName = 'auditbeat-custom-index-1'; - const expectedCount = mockStatsAuditbeatIndex[indexName].size_in_bytes; - - expect( - getTotalSizeInBytes({ - indexNames: [indexName], - stats: mockStatsAuditbeatIndex, - }) - ).toEqual(expectedCount); - }); - - test('it returns the expected total for indices', () => { - const expectedCount = Object.values(mockStatsPacketbeatIndex).reduce( - (acc, { size_in_bytes: sizeInBytes }) => { - return acc + (sizeInBytes ?? 0); - }, - 0 - ); - - expect( - getTotalSizeInBytes({ - indexNames: [ - '.ds-packetbeat-8.6.1-2023.02.04-000001', - '.ds-packetbeat-8.5.3-2023.02.04-000001', - ], - stats: mockStatsPacketbeatIndex, - }) - ).toEqual(expectedCount); - }); - }); - - describe('getIlmPhaseDescription', () => { - const phases: Array<{ - phase: string; - expected: string; - }> = [ - { - phase: 'hot', - expected: HOT_DESCRIPTION, - }, - { - phase: 'warm', - expected: WARM_DESCRIPTION, - }, - { - phase: 'cold', - expected: COLD_DESCRIPTION, - }, - { - phase: 'frozen', - expected: FROZEN_DESCRIPTION, - }, - { - phase: 'unmanaged', - expected: UNMANAGED_DESCRIPTION, - }, - { - phase: 'something-else', - expected: ' ', - }, - ]; - - phases.forEach(({ phase, expected }) => { - test(`it returns ${expected} when phase is ${phase}`, () => { - expect(getIlmPhaseDescription(phase)).toBe(expected); - }); - }); - }); - - describe('getPatternIlmPhaseDescription', () => { - const phases: Array<{ - expected: string; - indices: number; - pattern: string; - phase: string; - }> = [ - { - expected: - '1 index matching the .alerts-security.alerts-default pattern is hot. Hot indices are actively being updated and queried.', - indices: 1, - pattern: '.alerts-security.alerts-default', - phase: 'hot', - }, - { - expected: - '2 indices matching the .alerts-security.alerts-default pattern are hot. Hot indices are actively being updated and queried.', - indices: 2, - pattern: '.alerts-security.alerts-default', - phase: 'hot', - }, - { - expected: - '1 index matching the .alerts-security.alerts-default pattern is warm. Warm indices are no longer being updated but are still being queried.', - indices: 1, - pattern: '.alerts-security.alerts-default', - phase: 'warm', - }, - { - expected: - '2 indices matching the .alerts-security.alerts-default pattern are warm. Warm indices are no longer being updated but are still being queried.', - indices: 2, - pattern: '.alerts-security.alerts-default', - phase: 'warm', - }, - { - expected: - '1 index matching the .alerts-security.alerts-default pattern is cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', - indices: 1, - pattern: '.alerts-security.alerts-default', - phase: 'cold', - }, - { - expected: - '2 indices matching the .alerts-security.alerts-default pattern are cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', - indices: 2, - pattern: '.alerts-security.alerts-default', - phase: 'cold', - }, - { - expected: - "1 index matching the .alerts-security.alerts-default pattern is frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.", - indices: 1, - pattern: '.alerts-security.alerts-default', - phase: 'frozen', - }, - { - expected: - "2 indices matching the .alerts-security.alerts-default pattern are frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.", - indices: 2, - pattern: '.alerts-security.alerts-default', - phase: 'frozen', - }, - { - expected: - '1 index matching the .alerts-security.alerts-default pattern is unmanaged by Index Lifecycle Management (ILM)', - indices: 1, - pattern: '.alerts-security.alerts-default', - phase: 'unmanaged', - }, - { - expected: - '2 indices matching the .alerts-security.alerts-default pattern are unmanaged by Index Lifecycle Management (ILM)', - indices: 2, - pattern: '.alerts-security.alerts-default', - phase: 'unmanaged', - }, - { - expected: '', - indices: 1, - pattern: '.alerts-security.alerts-default', - phase: 'some-other-phase', - }, - { - expected: '', - indices: 2, - pattern: '.alerts-security.alerts-default', - phase: 'some-other-phase', - }, - ]; - - phases.forEach(({ expected, indices, pattern, phase }) => { - test(`it returns the expected description when indices is ${indices}, pattern is ${pattern}, and phase is ${phase}`, () => { - expect(getPatternIlmPhaseDescription({ indices, pattern, phase })).toBe(expected); - }); - }); - }); - - describe('getTotalPatternIncompatible', () => { - test('it returns zero when multiple indices in the results results have a count of zero', () => { - const results: Record = { - '.ds-packetbeat-8.5.3-2023.02.04-000001': { - docsCount: 1630289, - error: null, - ilmPhase: 'hot', - incompatible: 0, - indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'packetbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - '.ds-packetbeat-8.6.1-2023.02.04-000001': { - docsCount: 1628343, - error: null, - ilmPhase: 'hot', - incompatible: 0, - indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'packetbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - }; - - expect(getTotalPatternIncompatible(results)).toEqual(0); - }); - - test("it returns the expected total when some indices have incompatible fields, but others don't", () => { - const results: Record = { - '.ds-auditbeat-8.6.1-2023.02.07-000001': { - docsCount: 18086, - error: null, - ilmPhase: 'hot', - incompatible: 0, - indexName: '.ds-auditbeat-8.6.1-2023.02.07-000001', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'auditbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - 'auditbeat-custom-index-1': { - docsCount: 4, - error: null, - ilmPhase: 'unmanaged', - incompatible: 3, - indexName: 'auditbeat-custom-index-1', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'auditbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - 'auditbeat-custom-empty-index-1': { - docsCount: 0, - error: null, - ilmPhase: 'unmanaged', - incompatible: 1, - indexName: 'auditbeat-custom-empty-index-1', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'auditbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - }; - - expect(getTotalPatternIncompatible(results)).toEqual(4); - }); - - test('it returns the expected total when some indices have undefined incompatible counts', () => { - const results: Record = { - '.ds-auditbeat-8.6.1-2023.02.07-000001': { - docsCount: 18086, - error: null, - ilmPhase: 'hot', - incompatible: undefined, // <-- this index has an undefined `incompatible` - indexName: '.ds-auditbeat-8.6.1-2023.02.07-000001', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'auditbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - 'auditbeat-custom-index-1': { - docsCount: 4, - error: null, - ilmPhase: 'unmanaged', - incompatible: 3, - indexName: 'auditbeat-custom-index-1', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'auditbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - 'auditbeat-custom-empty-index-1': { - docsCount: 0, - error: null, - ilmPhase: 'unmanaged', - incompatible: 1, - indexName: 'auditbeat-custom-empty-index-1', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'auditbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }, - }; - - expect(getTotalPatternIncompatible(results)).toEqual(4); - }); - - test('it returns zero when `results` is empty', () => { - expect(getTotalPatternIncompatible({})).toEqual(0); - }); - - test('it returns undefined when `results` is undefined', () => { - expect(getTotalPatternIncompatible(undefined)).toBeUndefined(); - }); - }); - - describe('getTotalPatternIndicesChecked', () => { - test('it returns zero when `patternRollup` is undefined', () => { - expect(getTotalPatternIndicesChecked(undefined)).toEqual(0); - }); - - test('it returns zero when `patternRollup` does NOT have any results', () => { - expect(getTotalPatternIndicesChecked(auditbeatNoResults)).toEqual(0); - }); - - test('it returns the expected total when all indices in `patternRollup` have results', () => { - expect(getTotalPatternIndicesChecked(auditbeatWithAllResults)).toEqual(3); - }); - - test('it returns the expected total when some indices in `patternRollup` have errors', () => { - expect(getTotalPatternIndicesChecked(packetbeatWithSomeErrors)).toEqual(1); - }); - }); - - describe('getErrorSummary', () => { - test('it returns the expected error summary', () => { - const resultWithError: DataQualityCheckResult = { - docsCount: 1630289, - error: - 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', - ilmPhase: 'hot', - incompatible: undefined, - indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', - markdownComments: ['foo', 'bar', 'baz'], - pattern: 'packetbeat-*', - sameFamily: 0, - checkedAt: Date.now(), - }; - - expect(getErrorSummary(resultWithError)).toEqual({ - error: - 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', - indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', - pattern: 'packetbeat-*', - }); - }); - }); - - describe('getErrorSummariesForRollup', () => { - test('it returns the expected array of `ErrorSummary` when the `PatternRollup` contains errors', () => { - expect(getErrorSummariesForRollup(packetbeatWithSomeErrors)).toEqual([ - { - error: - 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', - indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', - pattern: 'packetbeat-*', - }, - ]); - }); - - test('it returns the an empty array of `ErrorSummary` when the `PatternRollup` contains all results, with NO errors', () => { - expect(getErrorSummariesForRollup(auditbeatWithAllResults)).toEqual([]); - }); - - test('it returns the an empty array of `ErrorSummary` when the `PatternRollup` has NO results', () => { - expect(getErrorSummariesForRollup(auditbeatNoResults)).toEqual([]); - }); - - test('it returns the an empty array of `ErrorSummary` when the `PatternRollup` is undefined', () => { - expect(getErrorSummariesForRollup(undefined)).toEqual([]); - }); - - test('it returns BOTH the expected (root) pattern-level error, and an index-level error when `PatternRollup` has both', () => { - const withPatternLevelError: PatternRollup = { - ...packetbeatWithSomeErrors, - error: 'This is a pattern-level error', - }; - - expect(getErrorSummariesForRollup(withPatternLevelError)).toEqual([ - { - error: 'This is a pattern-level error', - indexName: null, - pattern: 'packetbeat-*', - }, - { - error: - 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', - indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', - pattern: 'packetbeat-*', - }, - ]); - }); - - test('it returns the expected (root) pattern-level error when there are no index-level results', () => { - const withPatternLevelError: PatternRollup = { - ...auditbeatNoResults, - error: 'This is a pattern-level error', - }; - - expect(getErrorSummariesForRollup(withPatternLevelError)).toEqual([ - { - error: 'This is a pattern-level error', - indexName: null, - pattern: 'auditbeat-*', - }, - ]); - }); - }); - - describe('getErrorSummaries', () => { - test('it returns an empty array when patternRollups is empty', () => { - expect(getErrorSummaries({})).toEqual([]); - }); - - test('it returns an empty array when none of the patternRollups have errors', () => { - expect( - getErrorSummaries({ - '.alerts-security.alerts-default': alertIndexNoResults, - 'auditbeat-*': auditbeatWithAllResults, - 'packetbeat-*': packetbeatNoResults, - }) - ).toEqual([]); - }); - - test('it returns the expected array of `ErrorSummary` when some of the `PatternRollup` contain errors', () => { - expect( - getErrorSummaries({ - '.alerts-security.alerts-default': alertIndexNoResults, - 'auditbeat-*': auditbeatWithAllResults, - 'packetbeat-*': packetbeatWithSomeErrors, // <-- has errors - }) - ).toEqual([ - { - error: - 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', - indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', - pattern: 'packetbeat-*', - }, - ]); - }); - - test('it returns the expected array of `ErrorSummary` when there are both pattern-level and index-level errors', () => { - const withPatternLevelError: PatternRollup = { - ...auditbeatNoResults, - error: 'This is a pattern-level error', - }; - - expect( - getErrorSummaries({ - '.alerts-security.alerts-default': alertIndexNoResults, - 'auditbeat-*': withPatternLevelError, // <-- has pattern-level errors - 'packetbeat-*': packetbeatWithSomeErrors, // <-- has index-level errors - }) - ).toEqual([ - { - error: 'This is a pattern-level error', - indexName: null, - pattern: 'auditbeat-*', - }, - { - error: - 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', - indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', - pattern: 'packetbeat-*', - }, - ]); - }); - - test('it returns the expected array of `ErrorSummary` when there are just pattern-level errors', () => { - const withPatternLevelError: PatternRollup = { - ...auditbeatNoResults, - error: 'This is a pattern-level error', - }; - - expect( - getErrorSummaries({ - '.alerts-security.alerts-default': alertIndexNoResults, - 'auditbeat-*': withPatternLevelError, // <-- has pattern-level errors - 'packetbeat-*': packetbeatNoResults, - }) - ).toEqual([ - { - error: 'This is a pattern-level error', - indexName: null, - pattern: 'auditbeat-*', - }, - ]); - }); - }); - - describe('formatStorageResult', () => { - it('should correctly format the input data into a StorageResult object', () => { - const inputData: Parameters[number] = { - result: { - indexName: 'testIndex', - pattern: 'testPattern', - checkedAt: 1627545600000, - docsCount: 100, - incompatible: 3, - sameFamily: 1, - ilmPhase: 'hot', - markdownComments: ['test comments'], - error: null, - }, - report: { - batchId: 'testBatch', - isCheckAll: true, - sameFamilyFields: ['agent.type'], - unallowedMappingFields: ['event.category', 'host.name', 'source.ip'], - unallowedValueFields: ['event.category'], - sizeInBytes: 5000, - ecsVersion: '1.0.0', - indexName: 'testIndex', - indexId: 'testIndexId', - }, - partitionedFieldMetadata: mockPartitionedFieldMetadataWithSameFamily, - }; - - const expectedResult: StorageResult = { - batchId: 'testBatch', - indexName: 'testIndex', - indexPattern: 'testPattern', - isCheckAll: true, - checkedAt: 1627545600000, - docsCount: 100, - totalFieldCount: 10, - ecsFieldCount: 2, - customFieldCount: 4, - incompatibleFieldCount: 3, - incompatibleFieldMappingItems: [ - { - fieldName: 'event.category', - expectedValue: 'keyword', - actualValue: 'constant_keyword', - description: - 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', - }, - { - fieldName: 'host.name', - expectedValue: 'keyword', - actualValue: 'text', - description: - 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', - }, - { - fieldName: 'source.ip', - expectedValue: 'ip', - actualValue: 'text', - description: 'IP address of the source (IPv4 or IPv6).', - }, - ], - incompatibleFieldValueItems: [ - { - fieldName: 'event.category', - expectedValues: [ - 'authentication', - 'configuration', - 'database', - 'driver', - 'email', - 'file', - 'host', - 'iam', - 'intrusion_detection', - 'malware', - 'network', - 'package', - 'process', - 'registry', - 'session', - 'threat', - 'vulnerability', - 'web', - ], - actualValues: [{ name: 'an_invalid_category', count: 2 }], - description: - 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', - }, - ], - sameFamilyFieldCount: 1, - sameFamilyFields: ['agent.type'], - sameFamilyFieldItems: [ - { - fieldName: 'agent.type', - expectedValue: 'keyword', - actualValue: 'constant_keyword', - description: - 'Type of the agent.\nThe agent type always stays the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', - }, - ], - unallowedMappingFields: ['event.category', 'host.name', 'source.ip'], - unallowedValueFields: ['event.category'], - sizeInBytes: 5000, - ilmPhase: 'hot', - markdownComments: ['test comments'], - ecsVersion: '1.0.0', - indexId: 'testIndexId', - error: null, - }; - - expect(formatStorageResult(inputData)).toEqual(expectedResult); - }); - }); - - describe('postStorageResult', () => { - const { fetch } = httpServiceMock.createStartContract(); - const { toasts } = notificationServiceMock.createStartContract(); - beforeEach(() => { - fetch.mockClear(); - }); - - test('it posts the result', async () => { - const storageResult = { indexName: 'test' } as unknown as StorageResult; - await postStorageResult({ - storageResult, - httpFetch: fetch, - abortController: new AbortController(), - toasts, - }); - - expect(fetch).toHaveBeenCalledWith( - '/internal/ecs_data_quality_dashboard/results', - expect.objectContaining({ - method: 'POST', - body: JSON.stringify(storageResult), - }) - ); - }); - - test('it throws error', async () => { - const storageResult = { indexName: 'test' } as unknown as StorageResult; - fetch.mockRejectedValueOnce('test-error'); - await postStorageResult({ - httpFetch: fetch, - storageResult, - abortController: new AbortController(), - toasts, - }); - expect(toasts.addError).toHaveBeenCalledWith('test-error', { title: expect.any(String) }); - }); - }); - - describe('getStorageResults', () => { - const { fetch } = httpServiceMock.createStartContract(); - const { toasts } = notificationServiceMock.createStartContract(); - beforeEach(() => { - fetch.mockClear(); - }); - - test('it gets the results', async () => { - await getStorageResults({ - httpFetch: fetch, - abortController: new AbortController(), - pattern: 'auditbeat-*', - toasts, - }); - - expect(fetch).toHaveBeenCalledWith( - '/internal/ecs_data_quality_dashboard/results_latest/auditbeat-*', - expect.objectContaining({ - method: 'GET', - }) - ); - }); - - it('should catch error', async () => { - fetch.mockRejectedValueOnce('test-error'); - - const results = await getStorageResults({ - httpFetch: fetch, - abortController: new AbortController(), - pattern: 'auditbeat-*', - toasts, - }); - - expect(toasts.addError).toHaveBeenCalledWith('test-error', { title: expect.any(String) }); - expect(results).toEqual([]); - }); - }); -}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts deleted file mode 100644 index 9a56e00ba547a..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts +++ /dev/null @@ -1,615 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { HttpHandler } from '@kbn/core-http-browser'; -import type { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types'; -import { has, sortBy } from 'lodash/fp'; -import { IToasts } from '@kbn/core-notifications-browser'; -import { getIlmPhase } from './data_quality_panel/pattern/helpers'; - -import * as i18n from './translations'; - -import type { - DataQualityCheckResult, - DataQualityIndexCheckedParams, - EcsBasedFieldMetadata, - EnrichedFieldMetadata, - ErrorSummary, - IlmPhase, - IncompatibleFieldMappingItem, - IncompatibleFieldValueItem, - MeteringStatsIndex, - PartitionedFieldMetadata, - PartitionedFieldMetadataStats, - PatternRollup, - SameFamilyFieldItem, - UnallowedValueCount, -} from './types'; -import { EcsFlatTyped } from './constants'; - -const EMPTY_INDEX_NAMES: string[] = []; -export const INTERNAL_API_VERSION = '1'; -export const getIndexNames = ({ - ilmExplain, - ilmPhases, - isILMAvailable, - stats, -}: { - ilmExplain: Record | null; - ilmPhases: string[]; - isILMAvailable: boolean; - stats: Record | null; -}): string[] => { - if (((isILMAvailable && ilmExplain != null) || !isILMAvailable) && stats != null) { - const allIndexNames = Object.keys(stats); - const filteredByIlmPhase = isILMAvailable - ? allIndexNames.filter((indexName) => - ilmPhases.includes(getIlmPhase(ilmExplain?.[indexName], isILMAvailable) ?? '') - ) - : allIndexNames; - - return filteredByIlmPhase; - } else { - return EMPTY_INDEX_NAMES; - } -}; - -export interface FieldType { - field: string; - type: string; -} - -function shouldReadKeys(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -const getNextPathWithoutProperties = ({ - key, - pathWithoutProperties, - value, -}: { - key: string; - pathWithoutProperties: string; - value: unknown; -}): string => { - if (!pathWithoutProperties) { - return key; - } - - if (shouldReadKeys(value) && (key === 'properties' || key === 'fields')) { - return `${pathWithoutProperties}`; - } else { - return `${pathWithoutProperties}.${key}`; - } -}; - -export function getFieldTypes(mappingsProperties: Record): FieldType[] { - if (!shouldReadKeys(mappingsProperties)) { - throw new TypeError(`Root value is not flatten-able, received ${mappingsProperties}`); - } - - const result: FieldType[] = []; - (function flatten(prefix, object, pathWithoutProperties) { - for (const [key, value] of Object.entries(object)) { - const path = prefix ? `${prefix}.${key}` : key; - - const nextPathWithoutProperties = getNextPathWithoutProperties({ - key, - pathWithoutProperties, - value, - }); - - if (shouldReadKeys(value)) { - flatten(path, value, nextPathWithoutProperties); - } else { - if (nextPathWithoutProperties.endsWith('.type')) { - const pathWithoutType = nextPathWithoutProperties.slice( - 0, - nextPathWithoutProperties.lastIndexOf('.type') - ); - - result.push({ - field: pathWithoutType, - type: `${value}`, - }); - } - } - } - })('', mappingsProperties, ''); - - return result; -} - -/** - * Per https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html#_core_datatypes - * - * ``` - * Field types are grouped by _family_. Types in the same family have exactly - * the same search behavior but may have different space usage or - * performance characteristics. - * - * Currently, there are two type families, `keyword` and `text`. Other type - * families have only a single field type. For example, the `boolean` type - * family consists of one field type: `boolean`. - * ``` - */ -export const fieldTypeFamilies: Record> = { - keyword: new Set(['keyword', 'constant_keyword', 'wildcard']), - text: new Set(['text', 'match_only_text']), -}; - -export const getIsInSameFamily = ({ - ecsExpectedType, - type, -}: { - ecsExpectedType: string | undefined; - type: string; -}): boolean => { - if (ecsExpectedType != null) { - const allFamilies = Object.values(fieldTypeFamilies); - - return allFamilies.reduce( - (acc, family) => (acc !== true ? family.has(ecsExpectedType) && family.has(type) : acc), - false - ); - } else { - return false; - } -}; - -export const isMappingCompatible = ({ - ecsExpectedType, - type, -}: { - ecsExpectedType: string | undefined; - type: string; -}): boolean => type === ecsExpectedType; - -export const getEnrichedFieldMetadata = ({ - ecsMetadata, - fieldMetadata, - unallowedValues, -}: { - ecsMetadata: EcsFlatTyped; - fieldMetadata: FieldType; - unallowedValues: Record; -}): EnrichedFieldMetadata => { - const { field, type } = fieldMetadata; - const indexInvalidValues = unallowedValues[field] ?? []; - - if (has(fieldMetadata.field, ecsMetadata)) { - const ecsExpectedType = ecsMetadata[field].type; - const isEcsCompliant = - isMappingCompatible({ ecsExpectedType, type }) && indexInvalidValues.length === 0; - - const isInSameFamily = - !isMappingCompatible({ ecsExpectedType, type }) && - indexInvalidValues.length === 0 && - getIsInSameFamily({ ecsExpectedType, type }); - - return { - ...ecsMetadata[field], - indexFieldName: field, - indexFieldType: type, - indexInvalidValues, - hasEcsMetadata: true, - isEcsCompliant, - isInSameFamily, - }; - } else { - return { - indexFieldName: field, - indexFieldType: type, - indexInvalidValues: [], - hasEcsMetadata: false, - isEcsCompliant: false, - isInSameFamily: false, // custom fields are never in the same family - }; - } -}; - -export const getMissingTimestampFieldMetadata = (): EcsBasedFieldMetadata => ({ - ...EcsFlatTyped['@timestamp'], - hasEcsMetadata: true, - indexFieldName: '@timestamp', - indexFieldType: '-', - indexInvalidValues: [], - isEcsCompliant: false, - isInSameFamily: false, // `date` is not a member of any families -}); - -export const getPartitionedFieldMetadata = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): PartitionedFieldMetadata => - enrichedFieldMetadata.reduce( - (acc, x) => ({ - all: [...acc.all, x], - ecsCompliant: x.isEcsCompliant ? [...acc.ecsCompliant, x] : acc.ecsCompliant, - custom: !x.hasEcsMetadata ? [...acc.custom, x] : acc.custom, - incompatible: - x.hasEcsMetadata && !x.isEcsCompliant && !x.isInSameFamily - ? [...acc.incompatible, x] - : acc.incompatible, - sameFamily: x.isInSameFamily ? [...acc.sameFamily, x] : acc.sameFamily, - }), - { - all: [], - ecsCompliant: [], - custom: [], - incompatible: [], - sameFamily: [], - } - ); - -export const getPartitionedFieldMetadataStats = ( - partitionedFieldMetadata: PartitionedFieldMetadata -): PartitionedFieldMetadataStats => { - const { all, ecsCompliant, custom, incompatible, sameFamily } = partitionedFieldMetadata; - - return { - all: all.length, - ecsCompliant: ecsCompliant.length, - custom: custom.length, - incompatible: incompatible.length, - sameFamily: sameFamily.length, - }; -}; - -export const getDocsCount = ({ - indexName, - stats, -}: { - indexName: string; - stats: Record | null; -}): number => (stats && stats[indexName]?.num_docs) ?? 0; - -export const getIndexId = ({ - indexName, - stats, -}: { - indexName: string; - stats: Record | null; -}): string | null | undefined => stats && stats[indexName]?.uuid; - -export const getSizeInBytes = ({ - indexName, - stats, -}: { - indexName: string; - stats: Record | null; -}): number | undefined => (stats && stats[indexName]?.size_in_bytes) ?? undefined; - -export const getTotalDocsCount = ({ - indexNames, - stats, -}: { - indexNames: string[]; - stats: Record | null; -}): number => - indexNames.reduce( - (acc: number, indexName: string) => acc + getDocsCount({ stats, indexName }), - 0 - ); - -export const getTotalSizeInBytes = ({ - indexNames, - stats, -}: { - indexNames: string[]; - stats: Record | null; -}): number | undefined => { - let sum; - for (let i = 0; i < indexNames.length; i++) { - const currentSizeInBytes = getSizeInBytes({ stats, indexName: indexNames[i] }); - if (currentSizeInBytes != null) { - if (sum == null) { - sum = 0; - } - sum += currentSizeInBytes; - } else { - return undefined; - } - } - return sum; -}; - -export const EMPTY_STAT = '--'; - -/** - * Returns an i18n description of an an ILM phase - */ -export const getIlmPhaseDescription = (phase: string): string => { - switch (phase) { - case 'hot': - return i18n.HOT_DESCRIPTION; - case 'warm': - return i18n.WARM_DESCRIPTION; - case 'cold': - return i18n.COLD_DESCRIPTION; - case 'frozen': - return i18n.FROZEN_DESCRIPTION; - case 'unmanaged': - return i18n.UNMANAGED_DESCRIPTION; - default: - return ' '; - } -}; - -export const getPatternIlmPhaseDescription = ({ - indices, - pattern, - phase, -}: { - indices: number; - pattern: string; - phase: string; -}): string => { - switch (phase) { - case 'hot': - return i18n.HOT_PATTERN_TOOLTIP({ indices, pattern }); - case 'warm': - return i18n.WARM_PATTERN_TOOLTIP({ indices, pattern }); - case 'cold': - return i18n.COLD_PATTERN_TOOLTIP({ indices, pattern }); - case 'frozen': - return i18n.FROZEN_PATTERN_TOOLTIP({ indices, pattern }); - case 'unmanaged': - return i18n.UNMANAGED_PATTERN_TOOLTIP({ indices, pattern }); - default: - return ''; - } -}; - -export const getTotalPatternIncompatible = ( - results: Record | undefined -): number | undefined => { - if (results == null) { - return undefined; - } - - const allResults = Object.values(results); - - return allResults.reduce((acc, { incompatible }) => acc + (incompatible ?? 0), 0); -}; - -export const getTotalPatternIndicesChecked = (patternRollup: PatternRollup | undefined): number => { - if (patternRollup != null && patternRollup.results != null) { - const allResults = Object.values(patternRollup.results); - const nonErrorResults = allResults.filter(({ error }) => error == null); - - return nonErrorResults.length; - } else { - return 0; - } -}; - -export const getTotalPatternSameFamily = ( - results: Record | undefined -): number | undefined => { - if (results == null) { - return undefined; - } - - const allResults = Object.values(results); - - return allResults.reduce((acc, { sameFamily }) => acc + (sameFamily ?? 0), 0); -}; - -export const getIncompatibleStatBadgeColor = (incompatible: number | undefined): string => - incompatible != null && incompatible > 0 ? 'danger' : 'hollow'; - -export const getErrorSummary = ({ - error, - indexName, - pattern, -}: DataQualityCheckResult): ErrorSummary => ({ - error: String(error), - indexName, - pattern, -}); - -export const getErrorSummariesForRollup = ( - patternRollup: PatternRollup | undefined -): ErrorSummary[] => { - const maybePatternErrorSummary: ErrorSummary[] = - patternRollup != null && patternRollup.error != null - ? [{ pattern: patternRollup.pattern, indexName: null, error: patternRollup.error }] - : []; - - if (patternRollup != null && patternRollup.results != null) { - const unsortedResults: DataQualityCheckResult[] = Object.values(patternRollup.results); - const sortedResults = sortBy('indexName', unsortedResults); - - return sortedResults.reduce( - (acc, result) => [...acc, ...(result.error != null ? [getErrorSummary(result)] : [])], - maybePatternErrorSummary - ); - } else { - return maybePatternErrorSummary; - } -}; - -export const getErrorSummaries = ( - patternRollups: Record -): ErrorSummary[] => { - const allPatterns: string[] = Object.keys(patternRollups); - - // sort the patterns A-Z: - const sortedPatterns = [...allPatterns].sort((a, b) => { - return a.localeCompare(b); - }); - - return sortedPatterns.reduce( - (acc, pattern) => [...acc, ...getErrorSummariesForRollup(patternRollups[pattern])], - [] - ); -}; - -export const POST_INDEX_RESULTS = '/internal/ecs_data_quality_dashboard/results'; -export const GET_INDEX_RESULTS_LATEST = - '/internal/ecs_data_quality_dashboard/results_latest/{pattern}'; - -export interface StorageResult { - batchId: string; - indexName: string; - indexPattern: string; - isCheckAll: boolean; - checkedAt: number; - docsCount: number; - totalFieldCount: number; - ecsFieldCount: number; - customFieldCount: number; - incompatibleFieldCount: number; - incompatibleFieldMappingItems: IncompatibleFieldMappingItem[]; - incompatibleFieldValueItems: IncompatibleFieldValueItem[]; - sameFamilyFieldCount: number; - sameFamilyFields: string[]; - sameFamilyFieldItems: SameFamilyFieldItem[]; - unallowedMappingFields: string[]; - unallowedValueFields: string[]; - sizeInBytes: number; - ilmPhase?: IlmPhase; - markdownComments: string[]; - ecsVersion: string; - indexId: string; - error: string | null; -} - -export const formatStorageResult = ({ - result, - report, - partitionedFieldMetadata, -}: { - result: DataQualityCheckResult; - report: DataQualityIndexCheckedParams; - partitionedFieldMetadata: PartitionedFieldMetadata; -}): StorageResult => { - const incompatibleFieldMappingItems: IncompatibleFieldMappingItem[] = []; - const incompatibleFieldValueItems: IncompatibleFieldValueItem[] = []; - const sameFamilyFieldItems: SameFamilyFieldItem[] = []; - - partitionedFieldMetadata.incompatible.forEach((field) => { - if (field.type !== field.indexFieldType) { - incompatibleFieldMappingItems.push({ - fieldName: field.indexFieldName, - expectedValue: field.type, - actualValue: field.indexFieldType, - description: field.description, - }); - } - - if (field.indexInvalidValues.length > 0) { - incompatibleFieldValueItems.push({ - fieldName: field.indexFieldName, - expectedValues: field.allowed_values?.map((x) => x.name) ?? [], - actualValues: field.indexInvalidValues.map((v) => ({ name: v.fieldName, count: v.count })), - description: field.description, - }); - } - }); - - partitionedFieldMetadata.sameFamily.forEach((field) => { - sameFamilyFieldItems.push({ - fieldName: field.indexFieldName, - expectedValue: field.type, - actualValue: field.indexFieldType, - description: field.description, - }); - }); - - return { - batchId: report.batchId, - indexName: result.indexName, - indexPattern: result.pattern, - isCheckAll: report.isCheckAll, - checkedAt: result.checkedAt ?? Date.now(), - docsCount: result.docsCount ?? 0, - totalFieldCount: partitionedFieldMetadata.all.length, - ecsFieldCount: partitionedFieldMetadata.ecsCompliant.length, - customFieldCount: partitionedFieldMetadata.custom.length, - incompatibleFieldCount: partitionedFieldMetadata.incompatible.length, - incompatibleFieldMappingItems, - incompatibleFieldValueItems, - sameFamilyFieldCount: partitionedFieldMetadata.sameFamily.length, - sameFamilyFields: report.sameFamilyFields ?? [], - sameFamilyFieldItems, - unallowedMappingFields: report.unallowedMappingFields ?? [], - unallowedValueFields: report.unallowedValueFields ?? [], - sizeInBytes: report.sizeInBytes ?? 0, - ilmPhase: result.ilmPhase, - markdownComments: result.markdownComments, - ecsVersion: report.ecsVersion, - indexId: report.indexId ?? '', - error: result.error, - }; -}; - -export const formatResultFromStorage = ({ - storageResult, - pattern, -}: { - storageResult: StorageResult; - pattern: string; -}): DataQualityCheckResult => ({ - docsCount: storageResult.docsCount, - error: storageResult.error, - ilmPhase: storageResult.ilmPhase, - incompatible: storageResult.incompatibleFieldCount, - indexName: storageResult.indexName, - markdownComments: storageResult.markdownComments, - sameFamily: storageResult.sameFamilyFieldCount, - checkedAt: storageResult.checkedAt, - pattern, -}); - -export async function postStorageResult({ - storageResult, - httpFetch, - toasts, - abortController = new AbortController(), -}: { - storageResult: StorageResult; - httpFetch: HttpHandler; - toasts: IToasts; - abortController?: AbortController; -}): Promise { - try { - await httpFetch(POST_INDEX_RESULTS, { - method: 'POST', - signal: abortController.signal, - version: INTERNAL_API_VERSION, - body: JSON.stringify(storageResult), - }); - } catch (err) { - toasts.addError(err, { title: i18n.POST_RESULT_ERROR_TITLE }); - } -} - -export async function getStorageResults({ - pattern, - httpFetch, - toasts, - abortController, -}: { - pattern: string; - httpFetch: HttpHandler; - toasts: IToasts; - abortController: AbortController; -}): Promise { - try { - const route = GET_INDEX_RESULTS_LATEST.replace('{pattern}', pattern); - const results = await httpFetch(route, { - method: 'GET', - signal: abortController.signal, - version: INTERNAL_API_VERSION, - }); - return results; - } catch (err) { - toasts.addError(err, { title: i18n.GET_RESULTS_ERROR_TITLE }); - return []; - } -} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx deleted file mode 100644 index 53d576ccf6f70..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DARK_THEME } from '@elastic/charts'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { TestExternalProviders } from './mock/test_providers/test_providers'; -import { DataQualityPanel } from '.'; -import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; - -const { toasts } = notificationServiceMock.createSetupContract(); - -describe('DataQualityPanel', () => { - beforeEach(() => { - render( - - - - ); - }); - - test('it renders the body', () => { - expect(screen.getByTestId('body')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.test.tsx deleted file mode 100644 index 86b3ef1688e6a..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { renderHook } from '@testing-library/react-hooks'; -import React, { FC, PropsWithChildren } from 'react'; - -import { DataQualityProvider } from '../data_quality_panel/data_quality_context'; -import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_response'; -import { ERROR_LOADING_MAPPINGS } from '../translations'; -import { useMappings, UseMappings } from '.'; -import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; -import { Theme } from '@elastic/charts'; - -const mockHttpFetch = jest.fn(); -const mockReportDataQualityIndexChecked = jest.fn(); -const mockReportDataQualityCheckAllClicked = jest.fn(); -const mockTelemetryEvents = { - reportDataQualityIndexChecked: mockReportDataQualityIndexChecked, - reportDataQualityCheckAllCompleted: mockReportDataQualityCheckAllClicked, -}; -const { toasts } = notificationServiceMock.createSetupContract(); - -const ContextWrapper: FC> = ({ children }) => ( - true)} - endDate={null} - formatBytes={jest.fn()} - formatNumber={jest.fn()} - isAssistantEnabled={true} - lastChecked={'2023-03-28T22:27:28.159Z'} - openCreateCaseFlyout={jest.fn()} - patterns={['auditbeat-*']} - setLastChecked={jest.fn()} - startDate={null} - theme={{ - background: { - color: '#000', - }, - }} - baseTheme={ - { - background: { - color: '#000', - }, - } as Theme - } - ilmPhases={['hot', 'warm', 'unmanaged']} - selectedIlmPhaseOptions={[ - { - label: 'Hot', - value: 'hot', - }, - { - label: 'Warm', - value: 'warm', - }, - { - label: 'Unmanaged', - value: 'unmanaged', - }, - ]} - setSelectedIlmPhaseOptions={jest.fn()} - > - {children} - -); - -const pattern = 'auditbeat-*'; - -describe('useMappings', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('successful response from the mappings api', () => { - let mappingsResult: UseMappings | undefined; - - beforeEach(async () => { - mockHttpFetch.mockResolvedValue(mockMappingsResponse); - - const { result, waitForNextUpdate } = renderHook(() => useMappings(pattern), { - wrapper: ContextWrapper, - }); - await waitForNextUpdate(); - mappingsResult = await result.current; - }); - - test('it returns the expected mappings', async () => { - expect(mappingsResult?.indexes).toEqual(mockMappingsResponse); - }); - - test('it returns loading: false, because the data has loaded', async () => { - expect(mappingsResult?.loading).toBe(false); - }); - - test('it returns a null error, because no errors occurred', async () => { - expect(mappingsResult?.error).toBeNull(); - }); - }); - - describe('fetch rejects with an error', () => { - let mappingsResult: UseMappings | undefined; - const errorMessage = 'simulated error'; - - beforeEach(async () => { - mockHttpFetch.mockRejectedValue(new Error(errorMessage)); - - const { result, waitForNextUpdate } = renderHook(() => useMappings(pattern), { - wrapper: ContextWrapper, - }); - await waitForNextUpdate(); - mappingsResult = await result.current; - }); - - test('it returns null mappings, because an error occurred', async () => { - expect(mappingsResult?.indexes).toBeNull(); - }); - - test('it returns loading: false, because data loading reached a terminal state', async () => { - expect(mappingsResult?.loading).toBe(false); - }); - - test('it returns the expected error', async () => { - expect(mappingsResult?.error).toEqual( - ERROR_LOADING_MAPPINGS({ details: errorMessage, patternOrIndexName: pattern }) - ); - }); - }); -}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx deleted file mode 100644 index f20809a248086..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; -import { useEffect, useState } from 'react'; - -import { useDataQualityContext } from '../data_quality_panel/data_quality_context'; -import { fetchMappings } from './helpers'; -import { useIsMounted } from '../use_is_mounted'; - -export interface UseMappings { - indexes: Record | null; - error: string | null; - loading: boolean; -} - -export const useMappings = (patternOrIndexName: string): UseMappings => { - const { isMountedRef } = useIsMounted(); - const [indexes, setIndexes] = useState | null>(null); - const { httpFetch } = useDataQualityContext(); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const abortController = new AbortController(); - - async function fetchData() { - try { - const response = await fetchMappings({ abortController, httpFetch, patternOrIndexName }); - - if (!abortController.signal.aborted) { - if (isMountedRef.current) { - setIndexes(response); - } - } - } catch (e) { - if (!abortController.signal.aborted) { - if (isMountedRef.current) { - setError(e.message); - } - } - } finally { - if (!abortController.signal.aborted) { - if (isMountedRef.current) { - setLoading(false); - } - } - } - } - - fetchData(); - - return () => { - abortController.abort(); - }; - }, [httpFetch, isMountedRef, patternOrIndexName, setError]); - - return { indexes, error, loading }; -}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/types.ts deleted file mode 100644 index e8f0124cd4a3f..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { OnCheckCompleted, PatternRollup } from '../types'; - -export interface UseResultsRollupReturnValue { - onCheckCompleted: OnCheckCompleted; - patternIndexNames: Record; - patternRollups: Record; - totalDocsCount: number | undefined; - totalIncompatible: number | undefined; - totalIndices: number | undefined; - totalIndicesChecked: number | undefined; - totalSameFamily: number | undefined; - totalSizeInBytes: number | undefined; - updatePatternIndexNames: ({ - indexNames, - pattern, - }: { - indexNames: string[]; - pattern: string; - }) => void; - updatePatternRollup: (patternRollup: PatternRollup) => void; -} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx deleted file mode 100644 index d2d216fd12293..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { renderHook } from '@testing-library/react-hooks'; -import React, { FC, PropsWithChildren } from 'react'; - -import { getUnallowedValueRequestItems } from '../data_quality_panel/allowed_values/helpers'; -import { DataQualityProvider } from '../data_quality_panel/data_quality_context'; -import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values'; -import { ERROR_LOADING_UNALLOWED_VALUES } from '../translations'; -import { UnallowedValueRequestItem } from '../types'; -import { useUnallowedValues, UseUnallowedValues } from '.'; -import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; -import { EcsFlatTyped } from '../constants'; -import { Theme } from '@elastic/charts'; - -const mockHttpFetch = jest.fn(); -const mockReportDataQualityIndexChecked = jest.fn(); -const mockReportDataQualityCheckAllClicked = jest.fn(); -const mockTelemetryEvents = { - reportDataQualityIndexChecked: mockReportDataQualityIndexChecked, - reportDataQualityCheckAllCompleted: mockReportDataQualityCheckAllClicked, -}; -const { toasts } = notificationServiceMock.createSetupContract(); - -const ContextWrapper: FC> = ({ children }) => ( - true)} - endDate={null} - formatBytes={jest.fn()} - formatNumber={jest.fn()} - isAssistantEnabled={true} - lastChecked={'2023-03-28T22:27:28.159Z'} - openCreateCaseFlyout={jest.fn()} - patterns={['auditbeat-*']} - setLastChecked={jest.fn()} - startDate={null} - theme={{ - background: { - color: '#000', - }, - }} - baseTheme={ - { - background: { - color: '#000', - }, - } as Theme - } - ilmPhases={['hot', 'warm', 'unmanaged']} - selectedIlmPhaseOptions={[ - { - label: 'Hot', - value: 'hot', - }, - { - label: 'Warm', - value: 'warm', - }, - { - label: 'Unmanaged', - value: 'unmanaged', - }, - ]} - setSelectedIlmPhaseOptions={jest.fn()} - > - {children} - -); - -const indexName = 'auditbeat-custom-index-1'; -const requestItems = getUnallowedValueRequestItems({ - ecsMetadata: EcsFlatTyped, - indexName, -}); - -describe('useUnallowedValues', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('when requestItems is empty', () => { - const emptyRequestItems: UnallowedValueRequestItem[] = []; - - test('it does NOT make an http request', async () => { - await renderHook( - () => - useUnallowedValues({ - indexName, - requestItems: emptyRequestItems, // <-- empty - }), - { - wrapper: ContextWrapper, - } - ); - - expect(mockHttpFetch).not.toBeCalled(); - }); - }); - - describe('successful response from the unallowed values api', () => { - let unallowedValuesResult: UseUnallowedValues | undefined; - - beforeEach(async () => { - mockHttpFetch.mockResolvedValue(mockUnallowedValuesResponse); - - const { result, waitForNextUpdate } = renderHook( - () => useUnallowedValues({ indexName, requestItems }), - { - wrapper: ContextWrapper, - } - ); - await waitForNextUpdate(); - unallowedValuesResult = await result.current; - }); - - test('it returns the expected unallowed values', async () => { - expect(unallowedValuesResult?.unallowedValues).toEqual({ - 'event.category': [ - { count: 2, fieldName: 'an_invalid_category' }, - { count: 1, fieldName: 'theory' }, - ], - 'event.kind': [], - 'event.outcome': [], - 'event.type': [], - }); - }); - - test('it returns loading: false, because the data has loaded', async () => { - expect(unallowedValuesResult?.loading).toBe(false); - }); - - test('it returns a null error, because no errors occurred', async () => { - expect(unallowedValuesResult?.error).toBeNull(); - }); - }); - - describe('fetch rejects with an error', () => { - let unallowedValuesResult: UseUnallowedValues | undefined; - const errorMessage = 'simulated error'; - - beforeEach(async () => { - mockHttpFetch.mockRejectedValue(new Error(errorMessage)); - - const { result, waitForNextUpdate } = renderHook( - () => useUnallowedValues({ indexName, requestItems }), - { - wrapper: ContextWrapper, - } - ); - await waitForNextUpdate(); - unallowedValuesResult = await result.current; - }); - - test('it returns null unallowed values, because an error occurred', async () => { - expect(unallowedValuesResult?.unallowedValues).toBeNull(); - }); - - test('it returns loading: false, because data loading reached a terminal state', async () => { - expect(unallowedValuesResult?.loading).toBe(false); - }); - - test('it returns the expected error', async () => { - expect(unallowedValuesResult?.error).toEqual( - ERROR_LOADING_UNALLOWED_VALUES({ details: errorMessage, indexName }) - ); - }); - }); -}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.tsx deleted file mode 100644 index 7f35da714ecd9..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useEffect, useState } from 'react'; - -import { useDataQualityContext } from '../data_quality_panel/data_quality_context'; -import { fetchUnallowedValues, getUnallowedValues } from './helpers'; -import type { UnallowedValueCount, UnallowedValueRequestItem } from '../types'; -import { useIsMounted } from '../use_is_mounted'; - -export interface UseUnallowedValues { - unallowedValues: Record | null; - error: string | null; - loading: boolean; - requestTime: number | undefined; -} - -export const useUnallowedValues = ({ - indexName, - requestItems, -}: { - indexName: string; - requestItems: UnallowedValueRequestItem[]; -}): UseUnallowedValues => { - const { isMountedRef } = useIsMounted(); - const [unallowedValues, setUnallowedValues] = useState | null>(null); - const { httpFetch } = useDataQualityContext(); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - const [requestTime, setRequestTime] = useState(); - useEffect(() => { - if (requestItems.length === 0) { - return; - } - - const abortController = new AbortController(); - - async function fetchData() { - const startTime = Date.now(); - - try { - const searchResults = await fetchUnallowedValues({ - abortController, - httpFetch, - indexName, - requestItems, - }); - - const unallowedValuesMap = getUnallowedValues({ - requestItems, - searchResults, - }); - - if (!abortController.signal.aborted) { - if (isMountedRef.current) { - setUnallowedValues(unallowedValuesMap); - } - } - } catch (e) { - if (!abortController.signal.aborted) { - if (isMountedRef.current) { - setError(e.message); - setRequestTime(Date.now() - startTime); - } - } - } finally { - if (!abortController.signal.aborted) { - if (isMountedRef.current) { - setLoading(false); - setRequestTime(Date.now() - startTime); - } - } - } - } - - fetchData(); - - return () => { - abortController.abort(); - }; - }, [httpFetch, indexName, isMountedRef, requestItems, setError]); - - return { unallowedValues, error, loading, requestTime }; -}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/hooks/use_add_to_new_case/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/hooks/use_add_to_new_case/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/hooks/use_add_to_new_case/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_add_to_new_case/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/hooks/use_add_to_new_case/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/add_to_new_case/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/index.test.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/add_to_new_case/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/index.test.tsx index 1fec0d39fbfd0..ba4aee43c32c7 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/add_to_new_case/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/index.test.tsx @@ -12,7 +12,7 @@ import { AddToNewCaseAction } from '.'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; +} from '../../mock/test_providers/test_providers'; import userEvent from '@testing-library/user-event'; describe('AddToNewCaseAction', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/add_to_new_case/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/index.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/add_to_new_case/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/index.tsx index 537f5b13a0f08..93d5b7ba7e20b 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/add_to_new_case/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/add_to_new_case/index.tsx @@ -9,9 +9,9 @@ import React, { useCallback } from 'react'; import { EuiIcon, EuiLink } from '@elastic/eui'; import { useDataQualityContext } from '../../data_quality_context'; -import { useAddToNewCase } from '../../../use_add_to_new_case'; +import { useAddToNewCase } from './hooks/use_add_to_new_case'; import { StyledLinkText } from '../styles'; -import { ADD_TO_NEW_CASE } from '../../../translations'; +import { ADD_TO_NEW_CASE } from '../../translations'; interface Props { markdownComment: string; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/index.test.tsx similarity index 93% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/index.test.tsx index 8d706aad92f1f..33e52a398e3c9 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/index.test.tsx @@ -12,7 +12,7 @@ import { ChatAction } from '.'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; +} from '../../mock/test_providers/test_providers'; describe('ChatAction', () => { it('should render new chat link', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/index.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/index.tsx index ca37bcaad97d5..fb990fc295bbf 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/index.tsx @@ -14,7 +14,7 @@ import { DATA_QUALITY_PROMPT_CONTEXT_PILL, DATA_QUALITY_PROMPT_CONTEXT_PILL_TOOLTIP, DATA_QUALITY_SUGGESTED_USER_PROMPT, -} from '../../../translations'; +} from '../../translations'; import { useDataQualityContext } from '../../data_quality_context'; import { ASK_ASSISTANT } from './translations'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/chat/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/chat/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/copy_to_clipboard/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/copy_to_clipboard/index.test.tsx similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/copy_to_clipboard/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/copy_to_clipboard/index.test.tsx index 6e2147293237a..05a44639b08ec 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/copy_to_clipboard/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/copy_to_clipboard/index.test.tsx @@ -14,7 +14,7 @@ import { CopyToClipboardAction } from '.'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; +} from '../../mock/test_providers/test_providers'; jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/copy_to_clipboard/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/copy_to_clipboard/index.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/copy_to_clipboard/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/copy_to_clipboard/index.tsx index abf85203a0db1..23df7dd8ad88e 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/copy_to_clipboard/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/copy_to_clipboard/index.tsx @@ -9,7 +9,7 @@ import React, { useCallback } from 'react'; import { EuiIcon, EuiLink, copyToClipboard } from '@elastic/eui'; import { useDataQualityContext } from '../../data_quality_context'; -import { COPIED_RESULTS_TOAST_TITLE, COPY_TO_CLIPBOARD } from '../../../translations'; +import { COPIED_RESULTS_TOAST_TITLE, COPY_TO_CLIPBOARD } from '../../translations'; import { StyledLinkText } from '../styles'; interface Props { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/index.test.tsx similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/index.test.tsx index 55f32280ddb9d..8d3d5954c7a9e 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/index.test.tsx @@ -12,7 +12,7 @@ import { Actions } from '.'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../mock/test_providers/test_providers'; +} from '../mock/test_providers/test_providers'; describe('Actions', () => { it('renders nothing by default', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/styles.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/actions/styles.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/actions/styles.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/constants.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/constants.ts similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/constants.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/constants.ts index c89c4dc5daa15..26fe2698f077c 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/constants.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/constants.ts @@ -38,3 +38,7 @@ export const ilmPhaseOptionsStatic: EuiComboBoxOptionOption[] = [ value: 'unmanaged', }, ]; + +export const EMPTY_STAT = '--'; + +export const INTERNAL_API_VERSION = '1'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/contexts/indices_check_context/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/contexts/indices_check_context/index.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/contexts/indices_check_context/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/contexts/indices_check_context/index.tsx index d6ecacf09ac4e..48863a25e2431 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/contexts/indices_check_context/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/contexts/indices_check_context/index.tsx @@ -7,7 +7,7 @@ import { createContext, useContext } from 'react'; -import { UseIndicesCheckReturnValue } from '../../use_indices_check/types'; +import { UseIndicesCheckReturnValue } from '../../hooks/use_indices_check/types'; export const IndicesCheckContext = createContext(null); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/contexts/results_rollup_context/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/contexts/results_rollup_context/index.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/contexts/results_rollup_context/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/contexts/results_rollup_context/index.tsx index 7771a06fbf7f8..d60c933768153 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/contexts/results_rollup_context/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/contexts/results_rollup_context/index.tsx @@ -7,7 +7,7 @@ import { createContext, useContext } from 'react'; -import { UseResultsRollupReturnValue } from '../../use_results_rollup/types'; +import { UseResultsRollupReturnValue } from '../../hooks/use_results_rollup/types'; export const ResultsRollupContext = createContext(null); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_context/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.test.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_context/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.test.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_context/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_context/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.tsx index 06620ecacd04d..762efef424a10 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_context/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_context/index.tsx @@ -12,7 +12,7 @@ import type { IToasts } from '@kbn/core-notifications-browser'; import { PartialTheme, Theme } from '@elastic/charts'; import { EuiComboBoxOptionOption } from '@elastic/eui'; -import type { TelemetryEvents } from '../../types'; +import type { TelemetryEvents } from '../types'; export interface DataQualityProviderProps { httpFetch: HttpHandler; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/index.test.tsx similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/index.test.tsx index 329ff56cd8326..a20e279e6ac19 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/index.test.tsx @@ -8,7 +8,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { TestExternalProviders } from '../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../mock/test_providers/test_providers'; import { IlmPhasesEmptyPrompt } from '.'; describe('IlmPhasesEmptyPrompt', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/index.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/index.tsx index 1720da6df8be0..c8a6e7385a122 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/index.tsx @@ -15,7 +15,7 @@ import { HOT_DESCRIPTION, UNMANAGED_DESCRIPTION, WARM_DESCRIPTION, -} from '../translations'; +} from '../../translations'; import * as i18n from './translations'; const Ul = styled.ul` diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/ilm_phases_empty_prompt/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/ilm_phases_empty_prompt/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/index.test.tsx similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/index.test.tsx index 399b5705d028d..dfe943215f909 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/index.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; +} from '../mock/test_providers/test_providers'; import { DataQualityDetails } from '.'; const ilmPhases = ['hot', 'warm', 'unmanaged']; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/index.tsx similarity index 86% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/index.tsx index 8f8686f0c03b2..a880d2a7d4f16 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/index.tsx @@ -7,11 +7,11 @@ import React, { useCallback, useState } from 'react'; -import { IlmPhasesEmptyPrompt } from '../../../ilm_phases_empty_prompt'; +import { IlmPhasesEmptyPrompt } from './ilm_phases_empty_prompt'; import { IndicesDetails } from './indices_details'; import { StorageDetails } from './storage_details'; -import { SelectedIndex } from '../../../types'; -import { useDataQualityContext } from '../../data_quality_context'; +import { SelectedIndex } from '../types'; +import { useDataQualityContext } from '../data_quality_context'; const DataQualityDetailsComponent: React.FC = () => { const { isILMAvailable, ilmPhases } = useDataQualityContext(); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.test.tsx similarity index 83% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.test.tsx index ddb17a64d1fc2..d5aaa1eea19ae 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.test.tsx @@ -9,15 +9,15 @@ import numeral from '@elastic/numeral'; import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; -import { EMPTY_STAT } from '../../../../helpers'; -import { alertIndexWithAllResults } from '../../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { packetbeatNoResults } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { EMPTY_STAT } from '../../constants'; +import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../../mock/test_providers/test_providers'; -import { PatternRollup } from '../../../../types'; +} from '../../mock/test_providers/test_providers'; +import { PatternRollup } from '../../types'; import { Props, IndicesDetails } from '.'; const defaultBytesFormat = '0,0.[0]b'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.tsx similarity index 85% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.tsx index 426e61513ee14..fd565d8fc7637 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/indices_details/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/index.tsx @@ -9,10 +9,10 @@ import { EuiFlexItem } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { useResultsRollupContext } from '../../../../contexts/results_rollup_context'; -import { Pattern } from '../../../pattern'; -import { SelectedIndex } from '../../../../types'; -import { useDataQualityContext } from '../../../data_quality_context'; +import { useResultsRollupContext } from '../../contexts/results_rollup_context'; +import { Pattern } from './pattern'; +import { SelectedIndex } from '../../types'; +import { useDataQualityContext } from '../../data_quality_context'; const StyledPatternWrapperFlexItem = styled(EuiFlexItem)` margin-bottom: ${({ theme }) => theme.eui.euiSize}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/error_empty_prompt/index.test.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/error_empty_prompt/index.test.tsx index ee3241cd74d8b..aad142baff3a4 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/error_empty_prompt/index.test.tsx @@ -8,7 +8,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../mock/test_providers/test_providers'; import { ErrorEmptyPrompt } from '.'; describe('ErrorEmptyPrompt', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/error_empty_prompt/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/error_empty_prompt/index.tsx index 3214b704dc685..3bac63944838b 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/error_empty_prompt/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/error_empty_prompt/index.tsx @@ -8,7 +8,7 @@ import { EuiCallOut, EuiCode } from '@elastic/eui'; import React from 'react'; -import * as i18n from '../data_quality_summary/errors_popover/translations'; +import * as i18n from '../../../../data_quality_summary/summary_actions/check_status/errors_popover/translations'; interface Props { title: string; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/helpers.test.ts similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/helpers.test.ts index f6a01533bcfbf..56dc6364ba845 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/helpers.test.ts @@ -24,13 +24,13 @@ import { shouldCreateIndexNames, shouldCreatePatternRollup, } from './helpers'; -import { mockIlmExplain } from '../../mock/ilm_explain/mock_ilm_explain'; -import { mockDataQualityCheckResult } from '../../mock/data_quality_check_result/mock_index'; -import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { mockStats } from '../../mock/stats/mock_stats'; -import { DataQualityCheckResult } from '../../types'; -import { getIndexNames, getTotalDocsCount } from '../../helpers'; +import { mockIlmExplain } from '../../../mock/ilm_explain/mock_ilm_explain'; +import { mockDataQualityCheckResult } from '../../../mock/data_quality_check_result/mock_index'; +import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { mockStats } from '../../../mock/stats/mock_stats'; +import { DataQualityCheckResult } from '../../../types'; import { IndexSummaryTableItem } from './types'; +import { getIndexNames, getPatternDocsCount } from './utils/stats'; const hot: IlmExplainLifecycleLifecycleExplainManaged = { index: '.ds-packetbeat-8.6.1-2023.02.04-000001', @@ -569,7 +569,7 @@ describe('helpers', () => { ilmPhases: ['hot', 'unmanaged'], isILMAvailable, }); - const newDocsCount = getTotalDocsCount({ indexNames: newIndexNames, stats: mockStats }); + const newDocsCount = getPatternDocsCount({ indexNames: newIndexNames, stats: mockStats }); test('it returns false when the `patternRollup.docsCount` equals newDocsCount', () => { expect( shouldCreatePatternRollup({ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/helpers.ts similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/helpers.ts index ab3e917246f1d..5470854fd2ff0 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/helpers.ts @@ -15,9 +15,9 @@ import type { PatternRollup, SortConfig, MeteringStatsIndex, -} from '../../types'; -import { getDocsCount, getSizeInBytes } from '../../helpers'; +} from '../../../types'; import { IndexSummaryTableItem } from './types'; +import { getDocsCount, getSizeInBytes } from '../../../utils/stats'; export const isManaged = ( ilmExplainRecord: IlmExplainLifecycleLifecycleExplain | undefined diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_ilm_explain/index.test.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_ilm_explain/index.test.tsx index b8c6092fea0d0..768845f023011 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_ilm_explain/index.test.tsx @@ -8,9 +8,9 @@ import { renderHook } from '@testing-library/react-hooks'; import React from 'react'; -import { DataQualityProvider } from '../data_quality_panel/data_quality_context'; -import { mockIlmExplain } from '../mock/ilm_explain/mock_ilm_explain'; -import { ERROR_LOADING_ILM_EXPLAIN } from '../translations'; +import { DataQualityProvider } from '../../../../../data_quality_context'; +import { mockIlmExplain } from '../../../../../mock/ilm_explain/mock_ilm_explain'; +import { ERROR_LOADING_ILM_EXPLAIN } from '../../../../../translations'; import { useIlmExplain, UseIlmExplain } from '.'; import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; import { Theme } from '@elastic/charts'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_ilm_explain/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_ilm_explain/index.tsx index 8477d710fea76..17d8b8d46dea7 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_ilm_explain/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_ilm_explain/index.tsx @@ -8,10 +8,10 @@ import type { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types'; import { useEffect, useState } from 'react'; -import { useDataQualityContext } from '../data_quality_panel/data_quality_context'; -import { INTERNAL_API_VERSION } from '../helpers'; -import * as i18n from '../translations'; -import { useIsMounted } from '../use_is_mounted'; +import { useDataQualityContext } from '../../../../../data_quality_context'; +import { INTERNAL_API_VERSION } from '../../../../../constants'; +import * as i18n from '../../../../../translations'; +import { useIsMounted } from '../../../../../hooks/use_is_mounted'; const ILM_EXPLAIN_ENDPOINT = '/internal/ecs_data_quality_dashboard/ilm_explain'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.test.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.test.tsx index c07bf9b8afd76..5982fa715adb9 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.test.tsx @@ -8,9 +8,9 @@ import { renderHook } from '@testing-library/react-hooks'; import React, { FC, PropsWithChildren } from 'react'; -import { DataQualityProvider } from '../data_quality_panel/data_quality_context'; -import { mockStatsAuditbeatIndex } from '../mock/stats/mock_stats_packetbeat_index'; -import { ERROR_LOADING_STATS } from '../translations'; +import { DataQualityProvider } from '../../../../../data_quality_context'; +import { mockStatsAuditbeatIndex } from '../../../../../mock/stats/mock_stats_packetbeat_index'; +import { ERROR_LOADING_STATS } from '../../../../../translations'; import { useStats, UseStats } from '.'; import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; import { Theme } from '@elastic/charts'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.tsx index 0de57ccd54568..2f1507ed47007 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_stats/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/hooks/use_stats/index.tsx @@ -8,11 +8,11 @@ import { useEffect, useState } from 'react'; import { HttpFetchQuery } from '@kbn/core/public'; -import { useDataQualityContext } from '../data_quality_panel/data_quality_context'; -import * as i18n from '../translations'; -import { INTERNAL_API_VERSION } from '../helpers'; -import { MeteringStatsIndex } from '../types'; -import { useIsMounted } from '../use_is_mounted'; +import { useDataQualityContext } from '../../../../../data_quality_context'; +import * as i18n from '../../../../../translations'; +import { INTERNAL_API_VERSION } from '../../../../../constants'; +import { MeteringStatsIndex } from '../../../../../types'; +import { useIsMounted } from '../../../../../hooks/use_is_mounted'; const STATS_ENDPOINT = '/internal/ecs_data_quality_dashboard/stats'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.test.tsx similarity index 91% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.test.tsx index 9fdd6f3acc606..cbed456f9cdd5 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.test.tsx @@ -9,13 +9,13 @@ import numeral from '@elastic/numeral'; import { render, screen } from '@testing-library/react'; import React, { ComponentProps } from 'react'; -import { EMPTY_STAT } from '../../helpers'; +import { EMPTY_STAT } from '../../../constants'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../mock/test_providers/test_providers'; +} from '../../../mock/test_providers/test_providers'; import { Pattern } from '.'; -import { getCheckState } from '../../stub/get_check_state'; +import { getCheckState } from '../../../stub/get_check_state'; const indexName = 'auditbeat-custom-index-1'; const defaultBytesFormat = '0,0.[0]b'; @@ -26,7 +26,7 @@ const defaultNumberFormat = '0,0.[000]'; const formatNumber = (value: number | undefined) => value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; -jest.mock('../../use_stats', () => ({ +jest.mock('./hooks/use_stats', () => ({ useStats: jest.fn(() => ({ stats: {}, error: null, @@ -34,7 +34,7 @@ jest.mock('../../use_stats', () => ({ })), })); -jest.mock('../../use_ilm_explain', () => ({ +jest.mock('./hooks/use_ilm_explain', () => ({ useIlmExplain: jest.fn(() => ({ error: null, ilmExplain: {}, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.tsx similarity index 90% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.tsx index 106d9bbe2e433..a59fe7f87d3a5 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index.tsx @@ -8,7 +8,7 @@ import { EuiSpacer, useGeneratedHtmlId } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { ErrorEmptyPrompt } from '../error_empty_prompt'; +import { ErrorEmptyPrompt } from './error_empty_prompt'; import { defaultSort, getIlmExplainPhaseCounts, @@ -18,27 +18,22 @@ import { shouldCreateIndexNames, shouldCreatePatternRollup, } from './helpers'; -import { - getIndexNames, - getTotalDocsCount, - getTotalPatternIncompatible, - getTotalPatternIndicesChecked, - getTotalSizeInBytes, -} from '../../helpers'; -import { LoadingEmptyPrompt } from '../loading_empty_prompt'; +import { getTotalPatternIncompatible, getTotalPatternIndicesChecked } from '../../../utils/stats'; +import { getIndexNames, getPatternDocsCount, getPatternSizeInBytes } from './utils/stats'; +import { LoadingEmptyPrompt } from './loading_empty_prompt'; import { PatternSummary } from './pattern_summary'; -import { RemoteClustersCallout } from '../remote_clusters_callout'; -import { SummaryTable } from '../summary_table'; -import { getSummaryTableColumns } from '../summary_table/helpers'; +import { RemoteClustersCallout } from './remote_clusters_callout'; +import { SummaryTable } from './summary_table'; +import { getSummaryTableColumns } from './summary_table/helpers'; import * as i18n from './translations'; -import type { PatternRollup, SelectedIndex, SortConfig } from '../../types'; -import { useIlmExplain } from '../../use_ilm_explain'; -import { useStats } from '../../use_stats'; -import { useDataQualityContext } from '../data_quality_context'; +import type { PatternRollup, SelectedIndex, SortConfig } from '../../../types'; +import { useIlmExplain } from './hooks/use_ilm_explain'; +import { useStats } from './hooks/use_stats'; +import { useDataQualityContext } from '../../../data_quality_context'; import { PatternAccordion, PatternAccordionChildren } from './styles'; import { IndexCheckFlyout } from './index_check_flyout'; -import { useResultsRollupContext } from '../../contexts/results_rollup_context'; -import { useIndicesCheckContext } from '../../contexts/indices_check_context'; +import { useResultsRollupContext } from '../../../contexts/results_rollup_context'; +import { useIndicesCheckContext } from '../../../contexts/indices_check_context'; const EMPTY_INDEX_NAMES: string[] = []; @@ -152,7 +147,7 @@ const PatternComponent: React.FC = ({ useEffect(() => { const newIndexNames = getIndexNames({ stats, ilmExplain, ilmPhases, isILMAvailable }); - const newDocsCount = getTotalDocsCount({ indexNames: newIndexNames, stats }); + const newDocsCount = getPatternDocsCount({ indexNames: newIndexNames, stats }); if ( shouldCreateIndexNames({ @@ -188,7 +183,7 @@ const PatternComponent: React.FC = ({ pattern, results: undefined, sizeInBytes: isILMAvailable - ? getTotalSizeInBytes({ + ? getPatternSizeInBytes({ indexNames: getIndexNames({ stats, ilmExplain, ilmPhases, isILMAvailable }), stats, }) ?? 0 diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/hooks/use_current_window_width/index.test.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/hooks/use_current_window_width/index.test.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/hooks/use_current_window_width/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_current_window_width/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/hooks/use_current_window_width/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.test.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.test.tsx index b73da31bbcf8c..0b6b1d62ada01 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.test.tsx @@ -14,10 +14,10 @@ import { IndexCheckFlyout } from '.'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; -import { mockIlmExplain } from '../../../mock/ilm_explain/mock_ilm_explain'; -import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { mockStats } from '../../../mock/stats/mock_stats'; +} from '../../../../mock/test_providers/test_providers'; +import { mockIlmExplain } from '../../../../mock/ilm_explain/mock_ilm_explain'; +import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { mockStats } from '../../../../mock/stats/mock_stats'; describe('IndexCheckFlyout', () => { beforeEach(() => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.tsx index b9d33c51cc495..6748fd0651799 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index.tsx @@ -21,15 +21,16 @@ import { } from '@elastic/eui'; import React, { useCallback, useEffect } from 'react'; import moment from 'moment'; -import { useIndicesCheckContext } from '../../../contexts/indices_check_context'; +import { getDocsCount, getSizeInBytes } from '../../../../utils/stats'; +import { useIndicesCheckContext } from '../../../../contexts/indices_check_context'; -import { EMPTY_STAT, getDocsCount, getSizeInBytes } from '../../../helpers'; -import { MeteringStatsIndex, PatternRollup } from '../../../types'; -import { useDataQualityContext } from '../../data_quality_context'; -import { IndexProperties } from '../../index_properties'; +import { EMPTY_STAT } from '../../../../constants'; +import { MeteringStatsIndex, PatternRollup } from '../../../../types'; +import { useDataQualityContext } from '../../../../data_quality_context'; +import { IndexProperties } from './index_properties'; import { getIlmPhase } from '../helpers'; -import { IndexResultBadge } from '../../index_result_badge'; -import { useCurrentWindowWidth } from '../../../use_current_window_width'; +import { IndexResultBadge } from '../index_result_badge'; +import { useCurrentWindowWidth } from './hooks/use_current_window_width'; import { CHECK_NOW } from './translations'; export interface Props { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_body.test.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_body.test.tsx index 19e6c47a2c365..cd16e9870906a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_body.test.tsx @@ -9,7 +9,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { EmptyPromptBody } from './empty_prompt_body'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../../mock/test_providers/test_providers'; describe('EmptyPromptBody', () => { const content = 'foo bar baz @baz'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_body.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_body.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_body.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_title.test.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_title.test.tsx index 760d16f8a87a5..0b146f7b7f123 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_title.test.tsx @@ -9,7 +9,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { EmptyPromptTitle } from './empty_prompt_title'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../../mock/test_providers/test_providers'; describe('EmptyPromptTitle', () => { const title = 'What is a great title?'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_title.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/empty_prompt_title.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/empty_prompt_title.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers.test.ts similarity index 82% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers.test.ts index 2ef3eb9c1c190..962fa7a825714 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers.test.ts @@ -5,14 +5,10 @@ * 2.0. */ -import { - getMappingsProperties, - getSortedPartitionedFieldMetadata, - hasAllDataFetchingCompleted, -} from './helpers'; -import { mockIndicesGetMappingIndexMappingRecords } from '../../mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record'; -import { mockMappingsProperties } from '../../mock/mappings_properties/mock_mappings_properties'; -import { EcsFlatTyped } from '../../constants'; +import { getMappingsProperties, getSortedPartitionedFieldMetadata } from './helpers'; +import { mockIndicesGetMappingIndexMappingRecords } from '../../../../../mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record'; +import { mockMappingsProperties } from '../../../../../mock/mappings_properties/mock_mappings_properties'; +import { EcsFlatTyped } from '../../../../../constants'; describe('helpers', () => { describe('getSortedPartitionedFieldMetadata', () => { @@ -250,42 +246,4 @@ describe('helpers', () => { ).toBeNull(); }); }); - - describe('hasAllDataFetchingCompleted', () => { - test('it returns false when both the mappings and unallowed values are loading', () => { - expect( - hasAllDataFetchingCompleted({ - loadingMappings: true, - loadingUnallowedValues: true, - }) - ).toBe(false); - }); - - test('it returns false when mappings are loading, and unallowed values are NOT loading', () => { - expect( - hasAllDataFetchingCompleted({ - loadingMappings: true, - loadingUnallowedValues: false, - }) - ).toBe(false); - }); - - test('it returns false when mappings are NOT loading, and unallowed values are loading', () => { - expect( - hasAllDataFetchingCompleted({ - loadingMappings: false, - loadingUnallowedValues: true, - }) - ).toBe(false); - }); - - test('it returns true when both the mappings and unallowed values have finished loading', () => { - expect( - hasAllDataFetchingCompleted({ - loadingMappings: false, - loadingUnallowedValues: false, - }) - ).toBe(true); - }); - }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers.ts similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers.ts index c0046da0ea814..cf4ae6562b8ed 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers.ts @@ -10,15 +10,15 @@ import type { MappingProperty, } from '@elastic/elasticsearch/lib/api/types'; import { sortBy } from 'lodash/fp'; -import { EcsFlatTyped } from '../../constants'; +import { EcsFlatTyped } from '../../../../../constants'; +import type { PartitionedFieldMetadata, UnallowedValueCount } from '../../../../../types'; import { getEnrichedFieldMetadata, getFieldTypes, getMissingTimestampFieldMetadata, getPartitionedFieldMetadata, -} from '../../helpers'; -import type { PartitionedFieldMetadata, UnallowedValueCount } from '../../types'; +} from './utils/metadata'; export const ALL_TAB_ID = 'allTab'; export const ECS_COMPLIANT_TAB_ID = 'ecsCompliantTab'; @@ -90,11 +90,3 @@ export const getMappingsProperties = ({ return null; }; - -export const hasAllDataFetchingCompleted = ({ - loadingMappings, - loadingUnallowedValues, -}: { - loadingMappings: boolean; - loadingUnallowedValues: boolean; -}): boolean => loadingMappings === false && loadingUnallowedValues === false; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index.test.tsx similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index.test.tsx index 4e991d7878ace..2d361ec0ed34f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index.test.tsx @@ -9,15 +9,15 @@ import numeral from '@elastic/numeral'; import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; -import { EMPTY_STAT } from '../../helpers'; -import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { EMPTY_STAT } from '../../../../../constants'; +import { auditbeatWithAllResults } from '../../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../mock/test_providers/test_providers'; +} from '../../../../../mock/test_providers/test_providers'; import { LOADING_MAPPINGS, LOADING_UNALLOWED_VALUES } from './translations'; import { IndexProperties, Props } from '.'; -import { getCheckState } from '../../stub/get_check_state'; +import { getCheckState } from '../../../../../stub/get_check_state'; const indexName = 'auditbeat-custom-index-1'; const defaultBytesFormat = '0,0.[0]b'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index.tsx similarity index 86% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index.tsx index f7b1bb37e36f1..f28d506cda0fa 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index.tsx @@ -8,15 +8,15 @@ import React from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { ErrorEmptyPrompt } from '../error_empty_prompt'; -import { LoadingEmptyPrompt } from '../loading_empty_prompt'; -import { getIndexPropertiesContainerId } from '../pattern/helpers'; +import { ErrorEmptyPrompt } from '../../error_empty_prompt'; +import { LoadingEmptyPrompt } from '../../loading_empty_prompt'; +import { getIndexPropertiesContainerId } from '../../helpers'; import * as i18n from './translations'; -import type { IlmPhase, PatternRollup } from '../../types'; -import { useIndicesCheckContext } from '../../contexts/indices_check_context'; +import type { IlmPhase, PatternRollup } from '../../../../../types'; +import { useIndicesCheckContext } from '../../../../../contexts/indices_check_context'; import { IndexCheckFields } from './index_check_fields'; import { IndexStatsPanel } from './index_stats_panel'; -import { useDataQualityContext } from '../data_quality_context'; +import { useDataQualityContext } from '../../../../../data_quality_context'; export interface Props { docsCount: number; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_check_fields/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/index.test.tsx similarity index 90% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_check_fields/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/index.test.tsx index e5ab5247ac41e..024a53087efae 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_check_fields/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/index.test.tsx @@ -12,8 +12,8 @@ import { IndexCheckFields } from '.'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; -import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +} from '../../../../../../mock/test_providers/test_providers'; +import { auditbeatWithAllResults } from '../../../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; import userEvent from '@testing-library/user-event'; describe('IndexCheckFields', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_check_fields/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/index.tsx similarity index 91% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_check_fields/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/index.tsx index 8fa4a37ea3ead..db6696e36212a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_check_fields/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/index.tsx @@ -9,11 +9,11 @@ import React, { useMemo, useState } from 'react'; import { EuiButtonGroup, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; -import { useDataQualityContext } from '../../data_quality_context'; -import { useIndicesCheckContext } from '../../../contexts/indices_check_context'; +import { useDataQualityContext } from '../../../../../../data_quality_context'; +import { useIndicesCheckContext } from '../../../../../../contexts/indices_check_context'; import { EMPTY_METADATA, INCOMPATIBLE_TAB_ID } from '../helpers'; -import { IlmPhase, PatternRollup } from '../../../types'; -import { getTabs } from '../../tabs/helpers'; +import { IlmPhase, PatternRollup } from '../../../../../../types'; +import { getTabs } from './tabs/helpers'; const StyledTabFlexGroup = styled(EuiFlexGroup)` width: 100%; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/all_tab/index.tsx similarity index 77% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/all_tab/index.tsx index c80d1f615f261..085a865917b42 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/all_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/all_tab/index.tsx @@ -9,12 +9,12 @@ import { EcsVersion } from '@elastic/ecs'; import { EuiCallOut, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; -import { CompareFieldsTable } from '../../../compare_fields_table'; -import { getCommonTableColumns } from '../../../compare_fields_table/get_common_table_columns'; -import { EmptyPromptBody } from '../../index_properties/empty_prompt_body'; -import { EmptyPromptTitle } from '../../index_properties/empty_prompt_title'; -import * as i18n from '../../index_properties/translations'; -import type { PartitionedFieldMetadata } from '../../../types'; +import { CompareFieldsTable } from '../compare_fields_table'; +import { getCommonTableColumns } from '../compare_fields_table/get_common_table_columns'; +import { EmptyPromptBody } from '../../../empty_prompt_body'; +import { EmptyPromptTitle } from '../../../empty_prompt_title'; +import * as i18n from '../../../translations'; +import type { PartitionedFieldMetadata } from '../../../../../../../../types'; interface Props { indexName: string; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/custom_callout/index.test.tsx similarity index 80% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/custom_callout/index.test.tsx index 88bb4ef18ac47..b83f1fdc7ca60 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/custom_callout/index.test.tsx @@ -9,12 +9,12 @@ import { EcsVersion } from '@elastic/ecs'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { ECS_IS_A_PERMISSIVE_SCHEMA } from '../../../index_properties/translations'; +import { ECS_IS_A_PERMISSIVE_SCHEMA } from '../../../../translations'; import { hostNameKeyword, someField, -} from '../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { TestExternalProviders } from '../../../../mock/test_providers/test_providers'; +} from '../../../../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; import { CustomCallout } from '.'; describe('CustomCallout', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/custom_callout/index.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/custom_callout/index.tsx index f15e2a108a82c..1d631fa15a371 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/custom_callout/index.tsx @@ -9,9 +9,9 @@ import { EcsVersion } from '@elastic/ecs'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import React from 'react'; -import type { CustomFieldMetadata } from '../../../../types'; +import type { CustomFieldMetadata } from '../../../../../../../../../types'; -import * as i18n from '../../../index_properties/translations'; +import * as i18n from '../../../../translations'; interface Props { customFieldMetadata: CustomFieldMetadata[]; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/incompatible_callout/index.test.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/incompatible_callout/index.test.tsx index 7e1d1f9eb3c02..ffb315c266669 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/incompatible_callout/index.test.tsx @@ -13,8 +13,8 @@ import { DETECTION_ENGINE_RULES_MAY_NOT_MATCH, MAPPINGS_THAT_CONFLICT_WITH_ECS, PAGES_MAY_NOT_DISPLAY_EVENTS, -} from '../../../index_properties/translations'; -import { TestExternalProviders } from '../../../../mock/test_providers/test_providers'; +} from '../../../../translations'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; import { IncompatibleCallout } from '.'; describe('IncompatibleCallout', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/incompatible_callout/index.tsx similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/incompatible_callout/index.tsx index 6a692e05f8e54..41a69eb69424a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/incompatible_callout/index.tsx @@ -10,7 +10,7 @@ import { EcsVersion } from '@elastic/ecs'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import React from 'react'; -import * as i18n from '../../../index_properties/translations'; +import * as i18n from '../../../../translations'; import { CalloutItem } from '../../styles'; const IncompatibleCalloutComponent: React.FC = () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/missing_timestamp_callout/index.tsx similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/missing_timestamp_callout/index.tsx index 1db9e3cb6a494..3e9b1f705d1eb 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/missing_timestamp_callout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/missing_timestamp_callout/index.tsx @@ -8,7 +8,7 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import React from 'react'; -import * as i18n from '../../../index_properties/translations'; +import * as i18n from '../../../../translations'; import { CalloutItem } from '../../styles'; interface Props { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/same_family_callout/index.test.tsx similarity index 82% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/same_family_callout/index.test.tsx index d93b5dab41201..39289f1a9294f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/same_family_callout/index.test.tsx @@ -9,10 +9,10 @@ import { EcsVersion } from '@elastic/ecs'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { FIELDS_WITH_MAPPINGS_SAME_FAMILY } from '../../../index_properties/translations'; -import { TestExternalProviders } from '../../../../mock/test_providers/test_providers'; +import { FIELDS_WITH_MAPPINGS_SAME_FAMILY } from '../../../../translations'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; import { SameFamilyCallout } from '.'; -import { mockPartitionedFieldMetadataWithSameFamily } from '../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family'; +import { mockPartitionedFieldMetadataWithSameFamily } from '../../../../../../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family'; describe('SameFamilyCallout', () => { beforeEach(() => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/same_family_callout/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/same_family_callout/index.tsx index f89ebdda19889..ba7bab71f228d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/callouts/same_family_callout/index.tsx @@ -9,8 +9,8 @@ import { EcsVersion } from '@elastic/ecs'; import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; -import * as i18n from '../../../index_properties/translations'; -import type { EcsBasedFieldMetadata } from '../../../../types'; +import * as i18n from '../../../../translations'; +import type { EcsBasedFieldMetadata } from '../../../../../../../../../types'; interface Props { ecsBasedFieldMetadata: EcsBasedFieldMetadata[]; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/ecs_allowed_values/index.test.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/ecs_allowed_values/index.test.tsx index 97b633eff4a64..da16307b9a23b 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/ecs_allowed_values/index.test.tsx @@ -8,8 +8,8 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { mockAllowedValues } from '../../mock/allowed_values/mock_allowed_values'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; +import { mockAllowedValues } from '../../../../../../../../../mock/allowed_values/mock_allowed_values'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; import { EcsAllowedValues } from '.'; describe('EcsAllowedValues', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/ecs_allowed_values/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/ecs_allowed_values/index.tsx index 8f63047bb91e1..07197641c9788 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/ecs_allowed_values/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/ecs_allowed_values/index.tsx @@ -9,8 +9,8 @@ import { EuiCode, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import { EMPTY_PLACEHOLDER } from '../helpers'; -import { CodeSuccess } from '../../styles'; -import type { AllowedValue } from '../../types'; +import { CodeSuccess } from '../../../../../../../../../styles'; +import type { AllowedValue } from '../../../../../../../../../types'; interface Props { allowedValues: AllowedValue[] | undefined; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_common_table_columns/index.test.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_common_table_columns/index.test.tsx index 1695f2587a674..6a7f522aa803f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_common_table_columns/index.test.tsx @@ -9,13 +9,13 @@ import { render, screen } from '@testing-library/react'; import { omit } from 'lodash/fp'; import React from 'react'; -import { SAME_FAMILY } from '../../data_quality_panel/same_family/translations'; +import { SAME_FAMILY } from '../same_family/translations'; import { eventCategory, someField, eventCategoryWithUnallowedValues, -} from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; +} from '../../../../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; import { DOCUMENT_VALUES_ACTUAL, ECS_DESCRIPTION, @@ -24,7 +24,7 @@ import { FIELD, INDEX_MAPPING_TYPE_ACTUAL, } from '../translations'; -import { EnrichedFieldMetadata } from '../../types'; +import { EnrichedFieldMetadata } from '../../../../../../../../../types'; import { EMPTY_PLACEHOLDER, getCommonTableColumns } from '.'; describe('getCommonTableColumns', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_common_table_columns/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_common_table_columns/index.tsx index 61762d6fc8182..757294cc89c6d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_common_table_columns/index.tsx @@ -9,13 +9,17 @@ import type { EuiTableFieldDataColumnType } from '@elastic/eui'; import { EuiCode } from '@elastic/eui'; import React from 'react'; -import { SameFamily } from '../../data_quality_panel/same_family'; +import { SameFamily } from '../same_family'; import { EcsAllowedValues } from '../ecs_allowed_values'; -import { getIsInSameFamily } from '../../helpers'; import { IndexInvalidValues } from '../index_invalid_values'; -import { CodeDanger, CodeSuccess } from '../../styles'; +import { CodeDanger, CodeSuccess } from '../../../../../../../../../styles'; import * as i18n from '../translations'; -import type { AllowedValue, EnrichedFieldMetadata, UnallowedValueCount } from '../../types'; +import type { + AllowedValue, + EnrichedFieldMetadata, + UnallowedValueCount, +} from '../../../../../../../../../types'; +import { getIsInSameFamily } from '../../../../utils/get_is_in_same_family'; export const EMPTY_PLACEHOLDER = '--'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx index 8a9272a717217..588affcfdbf21 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx @@ -9,10 +9,10 @@ import { render, screen } from '@testing-library/react'; import { omit } from 'lodash/fp'; import React from 'react'; -import { SAME_FAMILY } from '../../data_quality_panel/same_family/translations'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; -import { eventCategory } from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { EcsBasedFieldMetadata } from '../../types'; +import { SAME_FAMILY } from '../same_family/translations'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; +import { eventCategory } from '../../../../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { EcsBasedFieldMetadata } from '../../../../../../../../../types'; import { getIncompatibleMappingsTableColumns } from '.'; describe('getIncompatibleMappingsTableColumns', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx index 4592e22565d74..ba2e3e2035f68 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx @@ -8,10 +8,10 @@ import type { EuiTableFieldDataColumnType } from '@elastic/eui'; import React from 'react'; -import { SameFamily } from '../../data_quality_panel/same_family'; -import { CodeDanger, CodeSuccess } from '../../styles'; +import { SameFamily } from '../same_family'; +import { CodeDanger, CodeSuccess } from '../../../../../../../../../styles'; import * as i18n from '../translations'; -import type { EcsBasedFieldMetadata } from '../../types'; +import type { EcsBasedFieldMetadata } from '../../../../../../../../../types'; export const EMPTY_PLACEHOLDER = '--'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/helpers.test.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/helpers.test.tsx index 2bb1e72c8ac6a..8bf2861402c73 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/helpers.test.tsx @@ -18,8 +18,8 @@ import { eventCategory, eventCategoryWithUnallowedValues, someField, -} from '../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { TestExternalProviders } from '../mock/test_providers/test_providers'; +} from '../../../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { TestExternalProviders } from '../../../../../../../../mock/test_providers/test_providers'; describe('helpers', () => { describe('getCustomTableColumns', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/helpers.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/helpers.tsx index 2563ced06733c..26e3a038b1ffe 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/helpers.tsx @@ -11,14 +11,14 @@ import React from 'react'; import { EcsAllowedValues } from './ecs_allowed_values'; import { IndexInvalidValues } from './index_invalid_values'; -import { CodeSuccess } from '../styles'; +import { CodeSuccess } from '../../../../../../../../styles'; import * as i18n from './translations'; import type { AllowedValue, CustomFieldMetadata, EcsBasedFieldMetadata, UnallowedValueCount, -} from '../types'; +} from '../../../../../../../../types'; export const EMPTY_PLACEHOLDER = '--'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index.test.tsx similarity index 79% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index.test.tsx index f1dcced85619f..e7344dad4d55e 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index.test.tsx @@ -8,9 +8,9 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE } from '../data_quality_panel/tabs/incompatible_tab/translations'; -import { eventCategory } from '../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { TestExternalProviders } from '../mock/test_providers/test_providers'; +import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE } from '../incompatible_tab/translations'; +import { eventCategory } from '../../../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { TestExternalProviders } from '../../../../../../../../mock/test_providers/test_providers'; import { CompareFieldsTable } from '.'; import { getIncompatibleMappingsTableColumns } from './get_incompatible_mappings_table_columns'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index.tsx similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index.tsx index 460663fb28790..8874bd8594866 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index.tsx @@ -10,7 +10,7 @@ import { EuiInMemoryTable, EuiTitle, EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import * as i18n from './translations'; -import type { EnrichedFieldMetadata } from '../types'; +import type { EnrichedFieldMetadata } from '../../../../../../../../types'; const search: Search = { box: { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index_invalid_values/index.test.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index_invalid_values/index.test.tsx index 719c94bd85d58..8a53f4cdaf546 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index_invalid_values/index.test.tsx @@ -9,8 +9,8 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { EMPTY_PLACEHOLDER } from '../helpers'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; -import { UnallowedValueCount } from '../../types'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; +import { UnallowedValueCount } from '../../../../../../../../../types'; import { IndexInvalidValues } from '.'; describe('IndexInvalidValues', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index_invalid_values/index.tsx similarity index 91% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index_invalid_values/index.tsx index 2b58ea98b8b28..7f83876423ba9 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index_invalid_values/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/index_invalid_values/index.tsx @@ -10,8 +10,8 @@ import React from 'react'; import styled from 'styled-components'; import { EMPTY_PLACEHOLDER } from '../helpers'; -import { CodeDanger } from '../../styles'; -import type { UnallowedValueCount } from '../../types'; +import { CodeDanger } from '../../../../../../../../../styles'; +import type { UnallowedValueCount } from '../../../../../../../../../types'; const IndexInvalidValueFlexItem = styled(EuiFlexItem)` margin-bottom: ${({ theme }) => theme.eui.euiSizeXS}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/same_family/index.test.tsx similarity index 87% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/same_family/index.test.tsx index d1bea1a3312b3..58889ad1edb2e 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/same_family/index.test.tsx @@ -9,7 +9,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { SAME_FAMILY } from './translations'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../../../../../../mock/test_providers/test_providers'; import { SameFamily } from '.'; describe('SameFamily', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/same_family/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/same_family/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/same_family/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/same_family/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/same_family/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/compare_fields_table/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/helpers.test.ts similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/helpers.test.ts index 424664c314832..5061a818e17fd 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/helpers.test.ts @@ -8,7 +8,7 @@ import numeral from '@elastic/numeral'; import { EcsVersion } from '@elastic/ecs'; -import { ECS_IS_A_PERMISSIVE_SCHEMA } from '../../index_properties/translations'; +import { ECS_IS_A_PERMISSIVE_SCHEMA } from '../../../translations'; import { getAllCustomMarkdownComments, getCustomMarkdownComment, @@ -17,9 +17,9 @@ import { import { hostNameKeyword, someField, -} from '../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; -import { EMPTY_STAT } from '../../../helpers'; +} from '../../../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { mockPartitionedFieldMetadata } from '../../../../../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { EMPTY_STAT } from '../../../../../../../../constants'; const defaultBytesFormat = '0,0.[0]b'; const formatBytes = (value: number | undefined) => diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/helpers.ts similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/helpers.ts index 55cb4898b7951..4ccebaefaa180 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/helpers.ts @@ -7,7 +7,7 @@ import { EcsVersion } from '@elastic/ecs'; -import { FIELD, INDEX_MAPPING_TYPE } from '../../../compare_fields_table/translations'; +import { FIELD, INDEX_MAPPING_TYPE } from '../compare_fields_table/translations'; import { getSummaryMarkdownComment, getCustomMarkdownTableRows, @@ -15,9 +15,13 @@ import { getMarkdownTable, getTabCountsMarkdownComment, getSummaryTableMarkdownComment, -} from '../../index_properties/markdown/helpers'; -import * as i18n from '../../index_properties/translations'; -import type { CustomFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../../types'; +} from '../../../markdown/helpers'; +import * as i18n from '../../../translations'; +import type { + CustomFieldMetadata, + IlmPhase, + PartitionedFieldMetadata, +} from '../../../../../../../../types'; export const getCustomMarkdownComment = ({ customFieldMetadata, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/index.tsx similarity index 85% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/index.tsx index 8c786216bc55a..2d7437e8f0638 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/custom_tab/index.tsx @@ -9,14 +9,14 @@ import { EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import { CustomCallout } from '../callouts/custom_callout'; -import { CompareFieldsTable } from '../../../compare_fields_table'; -import { getCustomTableColumns } from '../../../compare_fields_table/helpers'; -import { EmptyPromptBody } from '../../index_properties/empty_prompt_body'; -import { EmptyPromptTitle } from '../../index_properties/empty_prompt_title'; +import { CompareFieldsTable } from '../compare_fields_table'; +import { getCustomTableColumns } from '../compare_fields_table/helpers'; +import { EmptyPromptBody } from '../../../empty_prompt_body'; +import { EmptyPromptTitle } from '../../../empty_prompt_title'; import { getAllCustomMarkdownComments, showCustomCallout } from './helpers'; -import * as i18n from '../../index_properties/translations'; -import type { IlmPhase, PartitionedFieldMetadata } from '../../../types'; -import { useDataQualityContext } from '../../data_quality_context'; +import * as i18n from '../../../translations'; +import type { IlmPhase, PartitionedFieldMetadata } from '../../../../../../../../types'; +import { useDataQualityContext } from '../../../../../../../../data_quality_context'; import { StickyActions } from '../sticky_actions'; interface Props { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/ecs_compliant_tab/index.tsx similarity index 85% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/ecs_compliant_tab/index.tsx index 39b932cd88e40..7455ae3f482b9 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/ecs_compliant_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/ecs_compliant_tab/index.tsx @@ -11,14 +11,14 @@ import { EuiCallOut, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { CompareFieldsTable } from '../../../compare_fields_table'; -import { getEcsCompliantTableColumns } from '../../../compare_fields_table/helpers'; -import { EmptyPromptBody } from '../../index_properties/empty_prompt_body'; -import { EmptyPromptTitle } from '../../index_properties/empty_prompt_title'; +import { CompareFieldsTable } from '../compare_fields_table'; +import { getEcsCompliantTableColumns } from '../compare_fields_table/helpers'; +import { EmptyPromptBody } from '../../../empty_prompt_body'; +import { EmptyPromptTitle } from '../../../empty_prompt_title'; import { showMissingTimestampCallout } from '../helpers'; import { CalloutItem } from '../styles'; -import * as i18n from '../../index_properties/translations'; -import type { PartitionedFieldMetadata } from '../../../types'; +import * as i18n from '../../../translations'; +import type { PartitionedFieldMetadata } from '../../../../../../../../types'; const EmptyPromptContainer = styled.div` width: 100%; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/helpers.test.tsx similarity index 90% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/helpers.test.tsx index 3891a3d661561..4c30702fcef36 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/helpers.test.tsx @@ -10,9 +10,9 @@ import { omit } from 'lodash/fp'; import { eventCategory, timestamp, -} from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { mockPartitionedFieldMetadata } from '../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; -import { mockStatsAuditbeatIndex } from '../../mock/stats/mock_stats_packetbeat_index'; +} from '../../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { mockPartitionedFieldMetadata } from '../../../../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { mockStatsAuditbeatIndex } from '../../../../../../../mock/stats/mock_stats_packetbeat_index'; import { getEcsCompliantBadgeColor, getMissingTimestampComment, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/helpers.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/helpers.tsx index 5220522350b07..fa4e63afc6f3b 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/helpers.tsx @@ -9,10 +9,11 @@ import { EuiBadge } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; +import { getSizeInBytes } from '../../../../../../../utils/stats'; +import { getIncompatibleStatBadgeColor } from '../../../../../../../utils/get_incompatible_stat_badge_color'; import { AllTab } from './all_tab'; import { CustomTab } from './custom_tab'; import { EcsCompliantTab } from './ecs_compliant_tab'; -import { getIncompatibleStatBadgeColor, getSizeInBytes } from '../../helpers'; import { IncompatibleTab } from './incompatible_tab'; import { ALL_TAB_ID, @@ -20,16 +21,16 @@ import { ECS_COMPLIANT_TAB_ID, INCOMPATIBLE_TAB_ID, SAME_FAMILY_TAB_ID, -} from '../index_properties/helpers'; -import { getMarkdownComment } from '../index_properties/markdown/helpers'; -import * as i18n from '../index_properties/translations'; +} from '../../helpers'; +import { getMarkdownComment } from '../../markdown/helpers'; +import * as i18n from '../../translations'; import { SameFamilyTab } from './same_family_tab'; import type { EcsBasedFieldMetadata, IlmPhase, MeteringStatsIndex, PartitionedFieldMetadata, -} from '../../types'; +} from '../../../../../../../types'; export const getMissingTimestampComment = (): string => getMarkdownComment({ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers.test.ts similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers.test.ts index 9851da18072a7..5d0e66daff88a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers.test.ts @@ -18,14 +18,14 @@ import { getIncompatibleValuesFields, showInvalidCallout, } from './helpers'; -import { EMPTY_STAT } from '../../../helpers'; +import { EMPTY_STAT } from '../../../../../../../../constants'; import { DETECTION_ENGINE_RULES_MAY_NOT_MATCH, MAPPINGS_THAT_CONFLICT_WITH_ECS, PAGES_MAY_NOT_DISPLAY_EVENTS, -} from '../../index_properties/translations'; -import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; -import { PartitionedFieldMetadata } from '../../../types'; +} from '../../../translations'; +import { mockPartitionedFieldMetadata } from '../../../../../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { PartitionedFieldMetadata } from '../../../../../../../../types'; describe('helpers', () => { describe('getIncompatibleFieldsMarkdownComment', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers.ts similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers.ts index c4c6dfd4a2a82..edd7152c6d492 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers.ts @@ -16,9 +16,13 @@ import { getSummaryTableMarkdownComment, getTabCountsMarkdownComment, escape, -} from '../../index_properties/markdown/helpers'; -import * as i18n from '../../index_properties/translations'; -import type { EcsBasedFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../../types'; +} from '../../../markdown/helpers'; +import * as i18n from '../../../translations'; +import type { + EcsBasedFieldMetadata, + IlmPhase, + PartitionedFieldMetadata, +} from '../../../../../../../../types'; import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE, INCOMPATIBLE_FIELD_VALUES_TABLE_TITLE, @@ -29,8 +33,8 @@ import { INDEX_MAPPING_TYPE_ACTUAL, DOCUMENT_VALUES_ACTUAL, ECS_VALUES_EXPECTED, -} from '../../../compare_fields_table/translations'; -import { getIsInSameFamily } from '../../../helpers'; +} from '../compare_fields_table/translations'; +import { getIsInSameFamily } from '../../../utils/get_is_in_same_family'; export const getIncompatibleFieldsMarkdownComment = (incompatible: number): string => getMarkdownComment({ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/index.tsx similarity index 87% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/index.tsx index 52559b3d5116c..d24e82192291f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/index.tsx @@ -9,24 +9,24 @@ import { EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import { IncompatibleCallout } from '../callouts/incompatible_callout'; -import { CompareFieldsTable } from '../../../compare_fields_table'; -import { getIncompatibleMappingsTableColumns } from '../../../compare_fields_table/get_incompatible_mappings_table_columns'; -import { getIncompatibleValuesTableColumns } from '../../../compare_fields_table/helpers'; -import { EmptyPromptBody } from '../../index_properties/empty_prompt_body'; -import { EmptyPromptTitle } from '../../index_properties/empty_prompt_title'; +import { CompareFieldsTable } from '../compare_fields_table'; +import { getIncompatibleMappingsTableColumns } from '../compare_fields_table/get_incompatible_mappings_table_columns'; +import { getIncompatibleValuesTableColumns } from '../compare_fields_table/helpers'; +import { EmptyPromptBody } from '../../../empty_prompt_body'; +import { EmptyPromptTitle } from '../../../empty_prompt_title'; import { getAllIncompatibleMarkdownComments, getIncompatibleMappings, getIncompatibleValues, showInvalidCallout, } from './helpers'; -import * as i18n from '../../index_properties/translations'; +import * as i18n from '../../../translations'; import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE, INCOMPATIBLE_FIELD_VALUES_TABLE_TITLE, } from './translations'; -import type { IlmPhase, PartitionedFieldMetadata } from '../../../types'; -import { useDataQualityContext } from '../../data_quality_context'; +import type { IlmPhase, PartitionedFieldMetadata } from '../../../../../../../../types'; +import { useDataQualityContext } from '../../../../../../../../data_quality_context'; import { StickyActions } from '../sticky_actions'; interface Props { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/helpers.test.ts similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/helpers.test.ts index 0af79eda5e312..6eedd81fae4a5 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/helpers.test.ts @@ -13,9 +13,9 @@ import { getSameFamilyMarkdownComment, getSameFamilyMarkdownTablesComment, } from './helpers'; -import { EMPTY_STAT } from '../../../helpers'; -import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; -import { mockPartitionedFieldMetadataWithSameFamily } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family'; +import { EMPTY_STAT } from '../../../../../../../../constants'; +import { mockPartitionedFieldMetadata } from '../../../../../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +import { mockPartitionedFieldMetadataWithSameFamily } from '../../../../../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family'; describe('helpers', () => { describe('getSameFamilyMarkdownComment', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/helpers.ts similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/helpers.ts index d6c8852bb33bb..3c90cb81a5906 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/helpers.ts @@ -11,7 +11,7 @@ import { FIELD, ECS_MAPPING_TYPE_EXPECTED, INDEX_MAPPING_TYPE_ACTUAL, -} from '../../../compare_fields_table/translations'; +} from '../compare_fields_table/translations'; import { getSummaryMarkdownComment, getIncompatibleMappingsMarkdownTableRows, @@ -19,10 +19,14 @@ import { getMarkdownTable, getSummaryTableMarkdownComment, getTabCountsMarkdownComment, -} from '../../index_properties/markdown/helpers'; -import * as i18n from '../../index_properties/translations'; +} from '../../../markdown/helpers'; +import * as i18n from '../../../translations'; import { SAME_FAMILY_FIELD_MAPPINGS_TABLE_TITLE } from './translations'; -import type { EcsBasedFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../../types'; +import type { + EcsBasedFieldMetadata, + IlmPhase, + PartitionedFieldMetadata, +} from '../../../../../../../../types'; export const getSameFamilyMarkdownComment = (fieldsInSameFamily: number): string => getMarkdownComment({ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/index.tsx similarity index 90% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/index.tsx index 37891a4257a5c..8d91e26a0da09 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/index.tsx @@ -9,12 +9,12 @@ import { EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import { SameFamilyCallout } from '../callouts/same_family_callout'; -import { CompareFieldsTable } from '../../../compare_fields_table'; -import { getIncompatibleMappingsTableColumns } from '../../../compare_fields_table/get_incompatible_mappings_table_columns'; -import { useDataQualityContext } from '../../data_quality_context'; +import { CompareFieldsTable } from '../compare_fields_table'; +import { getIncompatibleMappingsTableColumns } from '../compare_fields_table/get_incompatible_mappings_table_columns'; +import { useDataQualityContext } from '../../../../../../../../data_quality_context'; import { getAllSameFamilyMarkdownComments, getSameFamilyMappings } from './helpers'; import { SAME_FAMILY_FIELD_MAPPINGS_TABLE_TITLE } from './translations'; -import type { IlmPhase, PartitionedFieldMetadata } from '../../../types'; +import type { IlmPhase, PartitionedFieldMetadata } from '../../../../../../../../types'; import { StickyActions } from '../sticky_actions'; interface Props { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/same_family_tab/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/sticky_actions/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/sticky_actions/index.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/sticky_actions/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/sticky_actions/index.tsx index 57b17a7453dd0..1cd2630e720d1 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/sticky_actions/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/sticky_actions/index.tsx @@ -9,7 +9,7 @@ import React, { FC } from 'react'; import { EuiButtonEmpty } from '@elastic/eui'; import styled from 'styled-components'; -import { Actions } from '../../actions'; +import { Actions } from '../../../../../../../../actions'; export const CopyToClipboardButton = styled(EuiButtonEmpty)` margin-left: ${({ theme }) => theme.eui.euiSizeXS}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/styles.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/styles.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/styles.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_stats_panel/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_stats_panel/index.test.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_stats_panel/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_stats_panel/index.test.tsx index f878cd9de4f13..b589eb09e8d12 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_stats_panel/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_stats_panel/index.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; import { IndexStatsPanel } from '.'; -import { TestExternalProviders } from '../../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../../../mock/test_providers/test_providers'; describe('IndexStatsPanel', () => { it('renders stats panel', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_stats_panel/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_stats_panel/index.tsx similarity index 87% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_stats_panel/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_stats_panel/index.tsx index 03b20e8a4ce45..da5ff8efc4242 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index_stats_panel/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_stats_panel/index.tsx @@ -8,11 +8,12 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; + import { DOCS } from '../translations'; -import { ILM_PHASE } from '../../../translations'; -import { SIZE } from '../../summary_table/translations'; -import { Stat } from '../../pattern/pattern_summary/stats_rollup/stat'; -import { getIlmPhaseDescription } from '../../../helpers'; +import { ILM_PHASE } from '../../../../../../translations'; +import { SIZE } from '../../../summary_table/translations'; +import { Stat } from '../../../../../../stat'; +import { getIlmPhaseDescription } from '../../../../../../utils/get_ilm_phase_description'; const StyledFlexItem = styled(EuiFlexItem)` border-right: 1px solid ${({ theme }) => theme.eui.euiBorderColor}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers.test.ts similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers.test.ts index 8c14a214cabf3..5fe3dbed0bd25 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers.test.ts @@ -11,9 +11,13 @@ import { ECS_MAPPING_TYPE_EXPECTED, FIELD, INDEX_MAPPING_TYPE_ACTUAL, -} from '../../../compare_fields_table/translations'; -import { ERRORS } from '../../data_quality_summary/errors_popover/translations'; -import { ERROR, INDEX, PATTERN } from '../../data_quality_summary/errors_viewer/translations'; +} from '../index_check_fields/tabs/compare_fields_table/translations'; +import { ERRORS } from '../../../../../../data_quality_summary/summary_actions/check_status/errors_popover/translations'; +import { + ERROR, + INDEX, + PATTERN, +} from '../../../../../../data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/translations'; import { escape, escapePreserveNewlines, @@ -41,27 +45,27 @@ import { getSummaryTableMarkdownRow, getTabCountsMarkdownComment, } from './helpers'; -import { EMPTY_STAT } from '../../../helpers'; -import { mockAllowedValues } from '../../../mock/allowed_values/mock_allowed_values'; +import { EMPTY_STAT } from '../../../../../../constants'; +import { mockAllowedValues } from '../../../../../../mock/allowed_values/mock_allowed_values'; import { eventCategory, mockCustomFields, mockIncompatibleMappings, sourceIpWithTextMapping, -} from '../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; +} from '../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; +import { mockPartitionedFieldMetadata } from '../../../../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; import { auditbeatNoResults, auditbeatWithAllResults, -} from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { SAME_FAMILY } from '../../same_family/translations'; -import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE } from '../../tabs/incompatible_tab/translations'; +} from '../../../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { SAME_FAMILY } from '../index_check_fields/tabs/compare_fields_table/same_family/translations'; +import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE } from '../index_check_fields/tabs/incompatible_tab/translations'; import { EcsBasedFieldMetadata, ErrorSummary, PatternRollup, UnallowedValueCount, -} from '../../../types'; +} from '../../../../../../types'; const errorSummary: ErrorSummary[] = [ { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers.ts similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers.ts index bdbfdba4ddb35..cc32fab713881 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { + getTotalPatternIncompatible, + getTotalPatternIndicesChecked, +} from '../../../../../../utils/stats'; import { ERRORS_MAY_OCCUR, ERRORS_CALLOUT_SUMMARY, @@ -14,14 +18,16 @@ import { READ, THE_FOLLOWING_PRIVILEGES_ARE_REQUIRED, VIEW_INDEX_METADATA, -} from '../../data_quality_summary/errors_popover/translations'; +} from '../../../../../../data_quality_summary/summary_actions/check_status/errors_popover/translations'; +import { EMPTY_STAT } from '../../../../../../constants'; +import { SAME_FAMILY } from '../index_check_fields/tabs/compare_fields_table/same_family/translations'; import { - EMPTY_STAT, - getTotalPatternIncompatible, - getTotalPatternIndicesChecked, -} from '../../../helpers'; -import { SAME_FAMILY } from '../../same_family/translations'; -import { HOT, WARM, COLD, FROZEN, UNMANAGED } from '../../../ilm_phases_empty_prompt/translations'; + HOT, + WARM, + COLD, + FROZEN, + UNMANAGED, +} from '../../../../../ilm_phases_empty_prompt/translations'; import * as i18n from '../translations'; import type { AllowedValue, @@ -34,8 +40,8 @@ import type { PartitionedFieldMetadata, PatternRollup, UnallowedValueCount, -} from '../../../types'; -import { getDocsCountPercent } from '../../summary_table/helpers'; +} from '../../../../../../types'; +import { getDocsCountPercent } from '../../../summary_table/helpers'; import { DOCS, ILM_PHASE, @@ -45,8 +51,8 @@ import { INDICES_CHECKED, RESULT, SIZE, -} from '../../summary_table/translations'; -import { DATA_QUALITY_TITLE } from '../../../translations'; +} from '../../../summary_table/translations'; +import { DATA_QUALITY_TITLE } from '../../../../../../translations'; export const EMPTY_PLACEHOLDER = '--'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/get_is_in_same_family.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/get_is_in_same_family.test.ts new file mode 100644 index 0000000000000..63388b15c9495 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/get_is_in_same_family.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getIsInSameFamily } from './get_is_in_same_family'; + +describe('getIsInSameFamily', () => { + test('it returns false when ecsExpectedType is undefined', () => { + expect(getIsInSameFamily({ ecsExpectedType: undefined, type: 'keyword' })).toBe(false); + }); + + const expectedFamilyMembers: { + [key: string]: string[]; + } = { + constant_keyword: ['keyword', 'wildcard'], // `keyword` and `wildcard` in the same family as `constant_keyword` + keyword: ['constant_keyword', 'wildcard'], + match_only_text: ['text'], + text: ['match_only_text'], + wildcard: ['keyword', 'constant_keyword'], + }; + + const ecsExpectedTypes = Object.keys(expectedFamilyMembers); + + ecsExpectedTypes.forEach((ecsExpectedType) => { + const otherMembersOfSameFamily = expectedFamilyMembers[ecsExpectedType]; + + otherMembersOfSameFamily.forEach((type) => + test(`it returns true for ecsExpectedType '${ecsExpectedType}' when given '${type}', a type in the same family`, () => { + expect(getIsInSameFamily({ ecsExpectedType, type })).toBe(true); + }) + ); + + test(`it returns false for ecsExpectedType '${ecsExpectedType}' when given 'date', a type NOT in the same family`, () => { + expect(getIsInSameFamily({ ecsExpectedType, type: 'date' })).toBe(false); + }); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/get_is_in_same_family.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/get_is_in_same_family.ts new file mode 100644 index 0000000000000..aa56bc472cbd0 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/get_is_in_same_family.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Per https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html#_core_datatypes + * + * ``` + * Field types are grouped by _family_. Types in the same family have exactly + * the same search behavior but may have different space usage or + * performance characteristics. + * + * Currently, there are two type families, `keyword` and `text`. Other type + * families have only a single field type. For example, the `boolean` type + * family consists of one field type: `boolean`. + * ``` + */ +export const fieldTypeFamilies: Record> = { + keyword: new Set(['keyword', 'constant_keyword', 'wildcard']), + text: new Set(['text', 'match_only_text']), +}; + +export const getIsInSameFamily = ({ + ecsExpectedType, + type, +}: { + ecsExpectedType: string | undefined; + type: string; +}): boolean => { + if (ecsExpectedType != null) { + const allFamilies = Object.values(fieldTypeFamilies); + + return allFamilies.reduce( + (acc, family) => (acc !== true ? family.has(ecsExpectedType) && family.has(type) : acc), + false + ); + } else { + return false; + } +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/metadata.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/metadata.test.ts new file mode 100644 index 0000000000000..d59e4821ed1c8 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/metadata.test.ts @@ -0,0 +1,402 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { omit } from 'lodash/fp'; + +import { + EnrichedFieldMetadata, + PartitionedFieldMetadata, + UnallowedValueCount, +} from '../../../../../../types'; +import { mockMappingsProperties } from '../../../../../../mock/mappings_properties/mock_mappings_properties'; +import { + FieldType, + getEnrichedFieldMetadata, + getFieldTypes, + getMissingTimestampFieldMetadata, + getPartitionedFieldMetadata, + isMappingCompatible, +} from './metadata'; +import { EcsFlatTyped } from '../../../../../../constants'; +import { + hostNameWithTextMapping, + hostNameKeyword, + someField, + someFieldKeyword, + sourceIpWithTextMapping, + sourceIpKeyword, + sourcePort, + timestamp, + eventCategoryWithUnallowedValues, +} from '../../../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; + +describe('getFieldTypes', () => { + const expected = [ + { + field: '@timestamp', + type: 'date', + }, + { + field: 'event.category', + type: 'keyword', + }, + { + field: 'host.name', + type: 'text', + }, + { + field: 'host.name.keyword', + type: 'keyword', + }, + { + field: 'some.field', + type: 'text', + }, + { + field: 'some.field.keyword', + type: 'keyword', + }, + { + field: 'source.ip', + type: 'text', + }, + { + field: 'source.ip.keyword', + type: 'keyword', + }, + { + field: 'source.port', + type: 'long', + }, + ]; + + test('it flattens the field names and types in the mapping properties', () => { + expect(getFieldTypes(mockMappingsProperties)).toEqual(expected); + }); + + test('it throws a type error when mappingsProperties is not flatten-able', () => { + // @ts-expect-error + const invalidType: Record = []; // <-- this is an array, NOT a valid Record + + expect(() => getFieldTypes(invalidType)).toThrowError('Root value is not flatten-able'); + }); +}); + +describe('isMappingCompatible', () => { + test('it returns true for an exact match', () => { + expect(isMappingCompatible({ ecsExpectedType: 'keyword', type: 'keyword' })).toBe(true); + }); + + test("it returns false when both types don't exactly match", () => { + expect(isMappingCompatible({ ecsExpectedType: 'wildcard', type: 'keyword' })).toBe(false); + }); +}); + +describe('getEnrichedFieldMetadata', () => { + /** + * The ECS schema + * https://raw.githubusercontent.com/elastic/ecs/main/generated/ecs/ecs_flat.yml + * defines a handful of fields that have `allowed_values`. For these + * fields, the documents in an index should only have specific values. + * + * This instance of the type `Record` + * represents an index that doesn't have any unallowed values, for the + * specified keys in the map, i.e. `event.category`, `event.kind`, etc. + * + * This will be used to test the happy path. Variants of this + * value will be used to test unhappy paths. + */ + const noUnallowedValues: Record = { + 'event.category': [], + 'event.kind': [], + 'event.outcome': [], + 'event.type': [], + }; + + /** + * Represents an index that has unallowed values, for the + * `event.category` field. The other fields in the collection, + * i.e. `event.kind`, don't have any unallowed values. + * + * This instance will be used to test paths where a field is + * NOT ECS complaint, because the index has unallowed values. + */ + const unallowedValues: Record = { + 'event.category': [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ], + 'event.kind': [], + 'event.outcome': [], + 'event.type': [], + }; + + /** + * This instance of a `FieldType` has the correct mapping for the + * `event.category` field. + * + * This instance will be used to test paths where the index has + * a valid mapping for the `event.category` field. + */ + const fieldMetadataCorrectMappingType: FieldType = { + field: 'event.category', + type: 'keyword', // <-- this index has the correct mapping type + }; + + /** + * This `EnrichedFieldMetadata` for the `event.category` field, + * represents a happy path result, where the index being checked: + * + * 1) The `type` of the field in the index, `keyword`, matches the expected + * `type` of the `event.category` field, as defined by the `EcsMetadata` + * 2) The index doesn't have any unallowed values for the `event.category` field + * + * Since this is a happy path result, it has the following values: + * `indexInvalidValues` is an empty array, because the index does not contain any invalid values + * `isEcsCompliant` is true, because the index has the expected mapping type, and no unallowed values + */ + const happyPathResultSample: EnrichedFieldMetadata = { + dashed_name: 'event-category', + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + example: 'authentication', + flat_name: 'event.category', + ignore_above: 1024, + level: 'core', + name: 'category', + normalize: ['array'], + short: 'Event category. The second categorization field in the hierarchy.', + type: 'keyword', + indexFieldName: 'event.category', + indexFieldType: 'keyword', // a valid mapping, because the `type` property from the `ecsMetadata` is also `keyword` + indexInvalidValues: [], // empty array, because the index does not contain any invalid values + hasEcsMetadata: true, + isEcsCompliant: true, // because the index has the expected mapping type, and no unallowed values + isInSameFamily: false, + }; + + /** + * Creates expected result matcher based on the happy path result sample. Please, add similar `expect` based assertions to it if anything breaks + * with an ECS upgrade, instead of hardcoding the values. + */ + const expectedResult = (extraFields: Record = {}) => + expect.objectContaining({ + ...happyPathResultSample, + ...extraFields, + allowed_values: expect.arrayContaining([ + expect.objectContaining({ + description: expect.any(String), + name: expect.any(String), + expected_event_types: expect.any(Array), + }), + ]), + }); + + test('it returns the happy path result when the index has no mapping conflicts, and no unallowed values', () => { + expect( + getEnrichedFieldMetadata({ + ecsMetadata: EcsFlatTyped, + fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index + unallowedValues: noUnallowedValues, // no unallowed values for `event.category` in this index + }) + ).toEqual(expectedResult()); + }); + + test('it returns the happy path result when the index has no mapping conflicts, and the unallowedValues map does not contain an entry for the field', () => { + // create an `unallowedValues` that doesn't have an entry for `event.category`: + const noEntryForEventCategory: Record = omit( + 'event.category', + unallowedValues + ); + + expect( + getEnrichedFieldMetadata({ + ecsMetadata: EcsFlatTyped, + fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index + unallowedValues: noEntryForEventCategory, // a lookup in this map for the `event.category` field will return undefined + }) + ).toEqual(expectedResult()); + }); + + test('it returns a result with the expected `indexInvalidValues` and `isEcsCompliant` when the index has no mapping conflict, but it has unallowed values', () => { + expect( + getEnrichedFieldMetadata({ + ecsMetadata: EcsFlatTyped, + fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index + unallowedValues, // this index has unallowed values for the event.category field + }) + ).toEqual( + expectedResult({ + indexInvalidValues: [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ], + isEcsCompliant: false, // because there are unallowed values + }) + ); + }); + + test('it returns a result with the expected `isEcsCompliant` and `isInSameFamily` when the index type does not match ECS, but NO unallowed values', () => { + const indexFieldType = 'text'; + + expect( + getEnrichedFieldMetadata({ + ecsMetadata: EcsFlatTyped, + fieldMetadata: { + field: 'event.category', // `event.category` is a `keyword`, per the ECS spec + type: indexFieldType, // this index has a mapping of `text` instead + }, + unallowedValues: noUnallowedValues, // no unallowed values for `event.category` in this index + }) + ).toEqual( + expectedResult({ + indexFieldType, + isEcsCompliant: false, // `keyword` !== `text` + isInSameFamily: false, // `keyword` and `text` are not in the same family + }) + ); + }); + + test('it returns a result with the expected `isEcsCompliant` and `isInSameFamily` when the mapping is is in the same family', () => { + const indexFieldType = 'wildcard'; + + expect( + getEnrichedFieldMetadata({ + ecsMetadata: EcsFlatTyped, + fieldMetadata: { + field: 'event.category', // `event.category` is a `keyword` per the ECS spec + type: indexFieldType, // this index has a mapping of `wildcard` instead + }, + unallowedValues: noUnallowedValues, // no unallowed values for `event.category` in this index + }) + ).toEqual( + expectedResult({ + indexFieldType, + isEcsCompliant: false, // `wildcard` !== `keyword` + isInSameFamily: true, // `wildcard` and `keyword` are in the same family + }) + ); + }); + + test('it returns a result with the expected `indexInvalidValues`,`isEcsCompliant`, and `isInSameFamily` when the index has BOTH mapping conflicts, and unallowed values', () => { + const indexFieldType = 'text'; + + expect( + getEnrichedFieldMetadata({ + ecsMetadata: EcsFlatTyped, + fieldMetadata: { + field: 'event.category', // `event.category` is a `keyword` per the ECS spec + type: indexFieldType, // this index has a mapping of `text` instead + }, + unallowedValues, // this index also has unallowed values for the event.category field + }) + ).toEqual( + expectedResult({ + indexFieldType, + indexInvalidValues: [ + { + count: 2, + fieldName: 'an_invalid_category', + }, + { + count: 1, + fieldName: 'theory', + }, + ], + isEcsCompliant: false, // because there are BOTH mapping conflicts and unallowed values + isInSameFamily: false, // `text` and `keyword` are not in the same family + }) + ); + }); + + test('it returns the expected result for a custom field, i.e. a field that does NOT have an entry in `ecsMetadata`', () => { + const field = 'a_custom_field'; // not defined by ECS + const indexFieldType = 'keyword'; + + expect( + getEnrichedFieldMetadata({ + ecsMetadata: EcsFlatTyped, + fieldMetadata: { + field, + type: indexFieldType, // no mapping conflict, because ECS doesn't define this field + }, + unallowedValues: noUnallowedValues, // no unallowed values for `a_custom_field` in this index + }) + ).toEqual({ + indexFieldName: field, + indexFieldType, + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, // custom fields are never in the same family + }); + }); +}); + +describe('getMissingTimestampFieldMetadata', () => { + test('it returns the expected `EnrichedFieldMetadata`', () => { + expect(getMissingTimestampFieldMetadata()).toEqual({ + ...EcsFlatTyped['@timestamp'], + hasEcsMetadata: true, + indexFieldName: '@timestamp', + indexFieldType: '-', // the index did NOT define a mapping for @timestamp + indexInvalidValues: [], + isEcsCompliant: false, // an index must define the @timestamp mapping + isInSameFamily: false, // `date` is not a member of any families + }); + }); +}); + +describe('getPartitionedFieldMetadata', () => { + test('it places all the `EnrichedFieldMetadata` in the expected categories', () => { + const enrichedFieldMetadata: EnrichedFieldMetadata[] = [ + timestamp, + eventCategoryWithUnallowedValues, + hostNameWithTextMapping, + hostNameKeyword, + someField, + someFieldKeyword, + sourceIpWithTextMapping, + sourceIpKeyword, + sourcePort, + ]; + const expected: PartitionedFieldMetadata = { + all: [ + timestamp, + eventCategoryWithUnallowedValues, + hostNameWithTextMapping, + hostNameKeyword, + someField, + someFieldKeyword, + sourceIpWithTextMapping, + sourceIpKeyword, + sourcePort, + ], + ecsCompliant: [timestamp, sourcePort], + custom: [hostNameKeyword, someField, someFieldKeyword, sourceIpKeyword], + incompatible: [ + eventCategoryWithUnallowedValues, + hostNameWithTextMapping, + sourceIpWithTextMapping, + ], + sameFamily: [], + }; + + expect(getPartitionedFieldMetadata(enrichedFieldMetadata)).toEqual(expected); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/metadata.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/metadata.ts new file mode 100644 index 0000000000000..87adf1e2314e1 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/utils/metadata.ts @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { has } from 'lodash/fp'; + +import { EcsFlatTyped } from '../../../../../../constants'; +import { + EcsBasedFieldMetadata, + EnrichedFieldMetadata, + PartitionedFieldMetadata, + UnallowedValueCount, +} from '../../../../../../types'; +import { getIsInSameFamily } from './get_is_in_same_family'; + +export const getPartitionedFieldMetadata = ( + enrichedFieldMetadata: EnrichedFieldMetadata[] +): PartitionedFieldMetadata => + enrichedFieldMetadata.reduce( + (acc, x) => ({ + all: [...acc.all, x], + ecsCompliant: x.isEcsCompliant ? [...acc.ecsCompliant, x] : acc.ecsCompliant, + custom: !x.hasEcsMetadata ? [...acc.custom, x] : acc.custom, + incompatible: + x.hasEcsMetadata && !x.isEcsCompliant && !x.isInSameFamily + ? [...acc.incompatible, x] + : acc.incompatible, + sameFamily: x.isInSameFamily ? [...acc.sameFamily, x] : acc.sameFamily, + }), + { + all: [], + ecsCompliant: [], + custom: [], + incompatible: [], + sameFamily: [], + } + ); + +export interface FieldType { + field: string; + type: string; +} + +function shouldReadKeys(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +const getNextPathWithoutProperties = ({ + key, + pathWithoutProperties, + value, +}: { + key: string; + pathWithoutProperties: string; + value: unknown; +}): string => { + if (!pathWithoutProperties) { + return key; + } + + if (shouldReadKeys(value) && (key === 'properties' || key === 'fields')) { + return `${pathWithoutProperties}`; + } else { + return `${pathWithoutProperties}.${key}`; + } +}; + +export function getFieldTypes(mappingsProperties: Record): FieldType[] { + if (!shouldReadKeys(mappingsProperties)) { + throw new TypeError(`Root value is not flatten-able, received ${mappingsProperties}`); + } + + const result: FieldType[] = []; + (function flatten(prefix, object, pathWithoutProperties) { + for (const [key, value] of Object.entries(object)) { + const path = prefix ? `${prefix}.${key}` : key; + + const nextPathWithoutProperties = getNextPathWithoutProperties({ + key, + pathWithoutProperties, + value, + }); + + if (shouldReadKeys(value)) { + flatten(path, value, nextPathWithoutProperties); + } else { + if (nextPathWithoutProperties.endsWith('.type')) { + const pathWithoutType = nextPathWithoutProperties.slice( + 0, + nextPathWithoutProperties.lastIndexOf('.type') + ); + + result.push({ + field: pathWithoutType, + type: `${value}`, + }); + } + } + } + })('', mappingsProperties, ''); + + return result; +} + +export const isMappingCompatible = ({ + ecsExpectedType, + type, +}: { + ecsExpectedType: string | undefined; + type: string; +}): boolean => type === ecsExpectedType; + +export const getEnrichedFieldMetadata = ({ + ecsMetadata, + fieldMetadata, + unallowedValues, +}: { + ecsMetadata: EcsFlatTyped; + fieldMetadata: FieldType; + unallowedValues: Record; +}): EnrichedFieldMetadata => { + const { field, type } = fieldMetadata; + const indexInvalidValues = unallowedValues[field] ?? []; + + if (has(fieldMetadata.field, ecsMetadata)) { + const ecsExpectedType = ecsMetadata[field].type; + const isEcsCompliant = + isMappingCompatible({ ecsExpectedType, type }) && indexInvalidValues.length === 0; + + const isInSameFamily = + !isMappingCompatible({ ecsExpectedType, type }) && + indexInvalidValues.length === 0 && + getIsInSameFamily({ ecsExpectedType, type }); + + return { + ...ecsMetadata[field], + indexFieldName: field, + indexFieldType: type, + indexInvalidValues, + hasEcsMetadata: true, + isEcsCompliant, + isInSameFamily, + }; + } else { + return { + indexFieldName: field, + indexFieldType: type, + indexInvalidValues: [], + hasEcsMetadata: false, + isEcsCompliant: false, + isInSameFamily: false, // custom fields are never in the same family + }; + } +}; + +export const getMissingTimestampFieldMetadata = (): EcsBasedFieldMetadata => ({ + ...EcsFlatTyped['@timestamp'], + hasEcsMetadata: true, + indexFieldName: '@timestamp', + indexFieldType: '-', + indexInvalidValues: [], + isEcsCompliant: false, + isInSameFamily: false, // `date` is not a member of any families +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/index_check_flyout/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/helpers.test.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/helpers.test.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/helpers.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/helpers.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/index.test.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/index.test.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_result_badge/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_result_badge/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/loading_empty_prompt/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/loading_empty_prompt/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/loading_empty_prompt/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/index.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/index.tsx index c30fd3e7dc4ce..db4d95ba48b4f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/index.tsx @@ -8,9 +8,9 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import type { IlmExplainPhaseCounts } from '../../../types'; +import type { IlmExplainPhaseCounts } from '../../../../types'; import { PatternLabel } from './pattern_label'; -import { StatsRollup } from './stats_rollup'; +import { StatsRollup } from '../../../../stats_rollup'; interface Props { ilmExplainPhaseCounts: IlmExplainPhaseCounts | undefined; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/helpers.test.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/helpers.test.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/helpers.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/helpers.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/index.test.tsx similarity index 93% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/index.test.tsx index 23031b9210df1..f9e04fc707b01 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/index.test.tsx @@ -13,9 +13,9 @@ import { import { render, screen } from '@testing-library/react'; import React from 'react'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../../../mock/test_providers/test_providers'; import { IlmPhaseCounts } from '.'; -import { getIlmExplainPhaseCounts } from '../pattern/helpers'; +import { getIlmExplainPhaseCounts } from '../../../helpers'; const hot: IlmExplainLifecycleLifecycleExplainManaged = { index: '.ds-packetbeat-8.6.1-2023.02.04-000001', diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/index.tsx index b24014bb400e6..c1d1ead5e7891 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/ilm_phase_counts/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/index.tsx @@ -9,8 +9,8 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { getPatternIlmPhaseDescription } from '../../helpers'; -import type { IlmExplainPhaseCounts, IlmPhase } from '../../types'; +import type { IlmExplainPhaseCounts, IlmPhase } from '../../../../../../types'; +import { getPatternIlmPhaseDescription } from './utils/get_pattern_ilm_phase_description'; const PhaseCountsFlexGroup = styled(EuiFlexGroup)` display: inline-flex; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/translations.ts new file mode 100644 index 0000000000000..d6a87384c9ae4 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/translations.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const UNMANAGED_PATTERN_TOOLTIP = ({ + indices, + pattern, +}: { + indices: number; + pattern: string; +}) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.unmanagedPatternTooltip', { + values: { indices, pattern }, + defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} unmanaged by Index Lifecycle Management (ILM)`, + }); + +export const WARM_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.warmPatternTooltip', { + values: { indices, pattern }, + defaultMessage: + '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} warm. Warm indices are no longer being updated but are still being queried.', + }); + +export const HOT_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.hotPatternTooltip', { + values: { indices, pattern }, + defaultMessage: + '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} hot. Hot indices are actively being updated and queried.', + }); + +export const FROZEN_PATTERN_TOOLTIP = ({ + indices, + pattern, +}: { + indices: number; + pattern: string; +}) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.frozenPatternTooltip', { + values: { indices, pattern }, + defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.`, + }); + +export const COLD_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => + i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.coldPatternTooltip', { + values: { indices, pattern }, + defaultMessage: + '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', + }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/utils/get_pattern_ilm_phase_description.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/utils/get_pattern_ilm_phase_description.test.ts new file mode 100644 index 0000000000000..3faff162dbffa --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/utils/get_pattern_ilm_phase_description.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getPatternIlmPhaseDescription } from './get_pattern_ilm_phase_description'; + +describe('getPatternIlmPhaseDescription', () => { + const phases: Array<{ + expected: string; + indices: number; + pattern: string; + phase: string; + }> = [ + { + expected: + '1 index matching the .alerts-security.alerts-default pattern is hot. Hot indices are actively being updated and queried.', + indices: 1, + pattern: '.alerts-security.alerts-default', + phase: 'hot', + }, + { + expected: + '2 indices matching the .alerts-security.alerts-default pattern are hot. Hot indices are actively being updated and queried.', + indices: 2, + pattern: '.alerts-security.alerts-default', + phase: 'hot', + }, + { + expected: + '1 index matching the .alerts-security.alerts-default pattern is warm. Warm indices are no longer being updated but are still being queried.', + indices: 1, + pattern: '.alerts-security.alerts-default', + phase: 'warm', + }, + { + expected: + '2 indices matching the .alerts-security.alerts-default pattern are warm. Warm indices are no longer being updated but are still being queried.', + indices: 2, + pattern: '.alerts-security.alerts-default', + phase: 'warm', + }, + { + expected: + '1 index matching the .alerts-security.alerts-default pattern is cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', + indices: 1, + pattern: '.alerts-security.alerts-default', + phase: 'cold', + }, + { + expected: + '2 indices matching the .alerts-security.alerts-default pattern are cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', + indices: 2, + pattern: '.alerts-security.alerts-default', + phase: 'cold', + }, + { + expected: + "1 index matching the .alerts-security.alerts-default pattern is frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.", + indices: 1, + pattern: '.alerts-security.alerts-default', + phase: 'frozen', + }, + { + expected: + "2 indices matching the .alerts-security.alerts-default pattern are frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.", + indices: 2, + pattern: '.alerts-security.alerts-default', + phase: 'frozen', + }, + { + expected: + '1 index matching the .alerts-security.alerts-default pattern is unmanaged by Index Lifecycle Management (ILM)', + indices: 1, + pattern: '.alerts-security.alerts-default', + phase: 'unmanaged', + }, + { + expected: + '2 indices matching the .alerts-security.alerts-default pattern are unmanaged by Index Lifecycle Management (ILM)', + indices: 2, + pattern: '.alerts-security.alerts-default', + phase: 'unmanaged', + }, + { + expected: '', + indices: 1, + pattern: '.alerts-security.alerts-default', + phase: 'some-other-phase', + }, + { + expected: '', + indices: 2, + pattern: '.alerts-security.alerts-default', + phase: 'some-other-phase', + }, + ]; + + phases.forEach(({ expected, indices, pattern, phase }) => { + test(`it returns the expected description when indices is ${indices}, pattern is ${pattern}, and phase is ${phase}`, () => { + expect(getPatternIlmPhaseDescription({ indices, pattern, phase })).toBe(expected); + }); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/utils/get_pattern_ilm_phase_description.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/utils/get_pattern_ilm_phase_description.ts new file mode 100644 index 0000000000000..20adb0c0c5bf9 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/ilm_phase_counts/utils/get_pattern_ilm_phase_description.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as i18n from '../translations'; + +export const getPatternIlmPhaseDescription = ({ + indices, + pattern, + phase, +}: { + indices: number; + pattern: string; + phase: string; +}): string => { + switch (phase) { + case 'hot': + return i18n.HOT_PATTERN_TOOLTIP({ indices, pattern }); + case 'warm': + return i18n.WARM_PATTERN_TOOLTIP({ indices, pattern }); + case 'cold': + return i18n.COLD_PATTERN_TOOLTIP({ indices, pattern }); + case 'frozen': + return i18n.FROZEN_PATTERN_TOOLTIP({ indices, pattern }); + case 'unmanaged': + return i18n.UNMANAGED_PATTERN_TOOLTIP({ indices, pattern }); + default: + return ''; + } +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/index.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/index.tsx index 62ff66a873124..03fede1fb7675 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/index.tsx @@ -9,10 +9,10 @@ import { EuiTitle, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import { getPatternResultTooltip, showResult } from './helpers'; -import { IlmPhaseCounts } from '../../../ilm_phase_counts'; +import { IlmPhaseCounts } from './ilm_phase_counts'; import * as i18n from '../translations'; -import type { IlmExplainPhaseCounts } from '../../../../types'; -import { IndexResultBadge } from '../../../index_result_badge'; +import type { IlmExplainPhaseCounts } from '../../../../../types'; +import { IndexResultBadge } from '../../index_result_badge'; interface Props { incompatible: number | undefined; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/pattern_label/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/pattern_summary/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/remote_clusters_callout/index.test.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/remote_clusters_callout/index.test.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/remote_clusters_callout/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/remote_clusters_callout/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/remote_clusters_callout/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/remote_clusters_callout/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/remote_clusters_callout/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/styles.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/styles.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/styles.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/helpers.test.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/helpers.test.tsx index a4227f0631819..1efaa01d36f9e 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/helpers.test.tsx @@ -16,8 +16,8 @@ import userEvent from '@testing-library/user-event'; import { omit } from 'lodash/fp'; import React from 'react'; -import { TestExternalProviders } from '../../mock/test_providers/test_providers'; -import { EMPTY_STAT } from '../../helpers'; +import { TestExternalProviders } from '../../../../mock/test_providers/test_providers'; +import { EMPTY_STAT } from '../../../../constants'; import { getDocsCountPercent, getIncompatibleStatColor, @@ -28,8 +28,8 @@ import { getToggleButtonId, } from './helpers'; import { CHECK_INDEX, VIEW_CHECK_DETAILS } from './translations'; -import { IndexSummaryTableItem } from '../pattern/types'; -import { getCheckState } from '../../stub/get_check_state'; +import { IndexSummaryTableItem } from '../types'; +import { getCheckState } from '../../../../stub/get_check_state'; const defaultBytesFormat = '0,0.[0]b'; const formatBytes = (value: number | undefined) => diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/helpers.tsx similarity index 93% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/helpers.tsx index f8e5b8d1b271e..327c5b5e46667 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/helpers.tsx @@ -18,15 +18,16 @@ import moment from 'moment'; import styled from 'styled-components'; import { euiThemeVars } from '@kbn/ui-theme'; -import { EMPTY_STAT, getIlmPhaseDescription } from '../../helpers'; -import { INCOMPATIBLE_INDEX_TOOL_TIP } from '../stat_label/translations'; -import { INDEX_SIZE_TOOLTIP } from '../../translations'; +import { EMPTY_STAT } from '../../../../constants'; +import { getIlmPhaseDescription } from '../../../../utils/get_ilm_phase_description'; +import { INCOMPATIBLE_INDEX_TOOL_TIP } from '../../../../stat_label/translations'; +import { INDEX_SIZE_TOOLTIP } from '../../../../translations'; import * as i18n from './translations'; -import { IndexSummaryTableItem } from '../pattern/types'; -import { UseIndicesCheckCheckState } from '../../use_indices_check/types'; +import { IndexSummaryTableItem } from '../types'; +import { UseIndicesCheckCheckState } from '../../../../hooks/use_indices_check/types'; import { IndexResultBadge } from '../index_result_badge'; import { getIndexResultToolTip } from '../index_result_badge/helpers'; -import { Stat } from '../pattern/pattern_summary/stats_rollup/stat'; +import { Stat } from '../../../../stat'; const ProgressContainer = styled.div` width: 150px; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.test.tsx similarity index 86% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.test.tsx index 24d57f927e6ea..01d45a3784a5f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.test.tsx @@ -9,17 +9,17 @@ import numeral from '@elastic/numeral'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { EMPTY_STAT } from '../../helpers'; +import { EMPTY_STAT } from '../../../../constants'; import { getSummaryTableColumns } from './helpers'; -import { mockIlmExplain } from '../../mock/ilm_explain/mock_ilm_explain'; -import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { mockStats } from '../../mock/stats/mock_stats'; +import { mockIlmExplain } from '../../../../mock/ilm_explain/mock_ilm_explain'; +import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { mockStats } from '../../../../mock/stats/mock_stats'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../mock/test_providers/test_providers'; -import { getSummaryTableItems } from '../pattern/helpers'; -import { SortConfig } from '../../types'; +} from '../../../../mock/test_providers/test_providers'; +import { getSummaryTableItems } from '../helpers'; +import { SortConfig } from '../../../../types'; import { Props, SummaryTable } from '.'; const defaultBytesFormat = '0,0.[0]b'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.tsx similarity index 91% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.tsx index cc09109ea16f9..fad209fb29e54 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/index.tsx @@ -10,11 +10,11 @@ import { EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { getShowPagination } from './helpers'; -import { defaultSort, MIN_PAGE_SIZE } from '../pattern/helpers'; -import { SortConfig } from '../../types'; -import { useDataQualityContext } from '../data_quality_context'; -import { IndexSummaryTableItem } from '../pattern/types'; -import { UseIndicesCheckCheckState } from '../../use_indices_check/types'; +import { defaultSort, MIN_PAGE_SIZE } from '../helpers'; +import { SortConfig } from '../../../../types'; +import { useDataQualityContext } from '../../../../data_quality_context'; +import { IndexSummaryTableItem } from '../types'; +import { UseIndicesCheckCheckState } from '../../../../hooks/use_indices_check/types'; export interface Props { getTableColumns: ({ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/summary_table/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/types.ts similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/types.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/types.ts index b079976950f1b..e44300859bffd 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/types.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IlmPhase } from '../../types'; +import { IlmPhase } from '../../../types'; export interface IndexSummaryTableItem { docsCount: number; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/utils/stats.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/utils/stats.test.ts new file mode 100644 index 0000000000000..306c9f93d83b6 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/utils/stats.test.ts @@ -0,0 +1,234 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { omit } from 'lodash/fp'; + +import { mockStatsAuditbeatIndex } from '../../../../mock/stats/mock_stats_packetbeat_index'; +import { mockStatsPacketbeatIndex } from '../../../../mock/stats/mock_stats_auditbeat_index'; +import { mockIlmExplain } from '../../../../mock/ilm_explain/mock_ilm_explain'; +import { mockStats } from '../../../../mock/stats/mock_stats'; +import { getIndexNames, getPatternDocsCount, getPatternSizeInBytes } from './stats'; +import { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types'; + +describe('getIndexNames', () => { + const isILMAvailable = true; + const ilmPhases = ['hot', 'warm', 'unmanaged']; + + test('returns the expected index names when they have an ILM phase included in the ilmPhases list', () => { + expect( + getIndexNames({ + ilmExplain: mockIlmExplain, // <-- the mock indexes have 'hot' ILM phases + ilmPhases, + isILMAvailable, + stats: mockStats, + }) + ).toEqual([ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + 'auditbeat-custom-index-1', + ]); + }); + + test('returns the expected filtered index names when they do NOT have an ILM phase included in the ilmPhases list', () => { + expect( + getIndexNames({ + ilmExplain: mockIlmExplain, // <-- the mock indexes have 'hot' and 'unmanaged' ILM phases... + ilmPhases: ['warm', 'unmanaged'], // <-- ...but we don't ask for 'hot' + isILMAvailable, + stats: mockStats, + }) + ).toEqual(['auditbeat-custom-index-1']); // <-- the 'unmanaged' index + }); + + test('returns the expected index names when the `ilmExplain` is missing a record for an index', () => { + // the following `ilmExplain` is missing a record for one of the two packetbeat indexes: + const ilmExplainWithMissingIndex: Record = omit( + '.ds-packetbeat-8.6.1-2023.02.04-000001', + mockIlmExplain + ); + + expect( + getIndexNames({ + ilmExplain: ilmExplainWithMissingIndex, // <-- the mock indexes have 'hot' ILM phases... + ilmPhases: ['hot', 'warm', 'unmanaged'], + isILMAvailable, + stats: mockStats, + }) + ).toEqual(['.ds-packetbeat-8.5.3-2023.02.04-000001', 'auditbeat-custom-index-1']); // <-- only includes two of the three indices, because the other one is missing an ILM explain record + }); + + test('returns empty index names when `ilmPhases` is empty', () => { + expect( + getIndexNames({ + ilmExplain: mockIlmExplain, + ilmPhases: [], + isILMAvailable, + stats: mockStats, + }) + ).toEqual([]); + }); + + test('returns empty index names when they have an ILM phase that matches', () => { + expect( + getIndexNames({ + ilmExplain: null, + ilmPhases, + isILMAvailable, + stats: mockStats, + }) + ).toEqual([]); + }); + + test('returns empty index names when just `stats` is null', () => { + expect( + getIndexNames({ + ilmExplain: mockIlmExplain, + ilmPhases, + isILMAvailable, + stats: null, + }) + ).toEqual([]); + }); + + test('returns empty index names when both `ilmExplain` and `stats` are null', () => { + expect( + getIndexNames({ + ilmExplain: null, + ilmPhases, + isILMAvailable, + stats: null, + }) + ).toEqual([]); + }); +}); + +describe('getPatternDocsCount', () => { + test('it returns the expected total given a subset of index names in the stats', () => { + const indexName = '.ds-packetbeat-8.5.3-2023.02.04-000001'; + const expectedCount = mockStatsPacketbeatIndex[indexName].num_docs; + + expect( + getPatternDocsCount({ + indexNames: [indexName], + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(expectedCount); + }); + + test('it returns the expected total given all index names in the stats', () => { + const allIndexNamesInStats = [ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + ]; + + expect( + getPatternDocsCount({ + indexNames: allIndexNamesInStats, + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(3258632); + }); + + test('it returns zero given an empty collection of index names', () => { + expect( + getPatternDocsCount({ + indexNames: [], // <-- empty + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(0); + }); + + test('it returns the expected total for a green index', () => { + const indexName = 'auditbeat-custom-index-1'; + const expectedCount = mockStatsAuditbeatIndex[indexName].num_docs; + + expect( + getPatternDocsCount({ + indexNames: [indexName], + stats: mockStatsAuditbeatIndex, + }) + ).toEqual(expectedCount); + }); +}); + +describe('getPatternSizeInBytes', () => { + test('it returns the expected total given a subset of index names in the stats', () => { + const indexName = '.ds-packetbeat-8.5.3-2023.02.04-000001'; + const expectedCount = mockStatsPacketbeatIndex[indexName].size_in_bytes; + + expect( + getPatternSizeInBytes({ + indexNames: [indexName], + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(expectedCount); + }); + + test('it returns the expected total given all index names in the stats', () => { + const allIndexNamesInStats = [ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + ]; + + expect( + getPatternSizeInBytes({ + indexNames: allIndexNamesInStats, + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(1464758182); + }); + + test('it returns undefined given an empty collection of index names', () => { + expect( + getPatternSizeInBytes({ + indexNames: [], // <-- empty + stats: mockStatsPacketbeatIndex, + }) + ).toBeUndefined(); + }); + + test('it returns undefined if sizeInByte in not an integer', () => { + const indexName = 'auditbeat-custom-index-1'; + + expect( + getPatternSizeInBytes({ + indexNames: [indexName], + stats: { [indexName]: { ...mockStatsAuditbeatIndex[indexName], size_in_bytes: null } }, + }) + ).toBeUndefined(); + }); + + test('it returns the expected total for an index', () => { + const indexName = 'auditbeat-custom-index-1'; + const expectedCount = mockStatsAuditbeatIndex[indexName].size_in_bytes; + + expect( + getPatternSizeInBytes({ + indexNames: [indexName], + stats: mockStatsAuditbeatIndex, + }) + ).toEqual(expectedCount); + }); + + test('it returns the expected total for indices', () => { + const expectedCount = Object.values(mockStatsPacketbeatIndex).reduce( + (acc, { size_in_bytes: sizeInBytes }) => { + return acc + (sizeInBytes ?? 0); + }, + 0 + ); + + expect( + getPatternSizeInBytes({ + indexNames: [ + '.ds-packetbeat-8.6.1-2023.02.04-000001', + '.ds-packetbeat-8.5.3-2023.02.04-000001', + ], + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(expectedCount); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/utils/stats.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/utils/stats.ts new file mode 100644 index 0000000000000..83e2b592dc079 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/utils/stats.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types'; + +import { getDocsCount, getSizeInBytes } from '../../../../utils/stats'; +import { MeteringStatsIndex } from '../../../../types'; +import { getIlmPhase } from '../helpers'; + +export const getPatternDocsCount = ({ + indexNames, + stats, +}: { + indexNames: string[]; + stats: Record | null; +}): number => + indexNames.reduce( + (acc: number, indexName: string) => acc + getDocsCount({ stats, indexName }), + 0 + ); + +export const getPatternSizeInBytes = ({ + indexNames, + stats, +}: { + indexNames: string[]; + stats: Record | null; +}): number | undefined => { + let sum; + for (let i = 0; i < indexNames.length; i++) { + const currentSizeInBytes = getSizeInBytes({ stats, indexName: indexNames[i] }); + if (currentSizeInBytes != null) { + if (sum == null) { + sum = 0; + } + sum += currentSizeInBytes; + } else { + return undefined; + } + } + return sum; +}; + +const EMPTY_INDEX_NAMES: string[] = []; +export const getIndexNames = ({ + ilmExplain, + ilmPhases, + isILMAvailable, + stats, +}: { + ilmExplain: Record | null; + ilmPhases: string[]; + isILMAvailable: boolean; + stats: Record | null; +}): string[] => { + if (((isILMAvailable && ilmExplain != null) || !isILMAvailable) && stats != null) { + const allIndexNames = Object.keys(stats); + const filteredByIlmPhase = isILMAvailable + ? allIndexNames.filter((indexName) => + ilmPhases.includes(getIlmPhase(ilmExplain?.[indexName], isILMAvailable) ?? '') + ) + : allIndexNames; + + return filteredByIlmPhase; + } else { + return EMPTY_INDEX_NAMES; + } +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/helpers.test.ts similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/helpers.test.ts index 650b70586d19f..da46cd2b40f33 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/helpers.test.ts @@ -8,7 +8,7 @@ import numeral from '@elastic/numeral'; import { euiThemeVars } from '@kbn/ui-theme'; -import { EMPTY_STAT } from '../../../../helpers'; +import { EMPTY_STAT } from '../../constants'; import { DEFAULT_INDEX_COLOR, getFillColor, @@ -21,10 +21,10 @@ import { getPatternLegendItem, getPatternSizeInBytes, } from './helpers'; -import { alertIndexWithAllResults } from '../../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { packetbeatNoResults } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; -import { PatternRollup } from '../../../../types'; +import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { PatternRollup } from '../../types'; const defaultBytesFormat = '0,0.[0]b'; const formatBytes = (value: number | undefined) => diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/helpers.ts similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/helpers.ts index 3eaf493222cb0..283854a62acf2 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/helpers.ts @@ -9,9 +9,9 @@ import type { Datum, Key, ArrayNode } from '@elastic/charts'; import { euiThemeVars } from '@kbn/ui-theme'; import { orderBy } from 'lodash/fp'; -import { getDocsCount, getSizeInBytes } from '../../../../helpers'; -import { getIlmPhase } from '../../../pattern/helpers'; -import { PatternRollup } from '../../../../types'; +import { getIlmPhase } from '../indices_details/pattern/helpers'; +import { PatternRollup } from '../../types'; +import { getDocsCount, getSizeInBytes } from '../../utils/stats'; export interface LegendItem { color: string | null; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/index.test.tsx similarity index 79% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/index.test.tsx index 5dd06ad340474..3ebb44a8306ef 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/index.test.tsx @@ -9,15 +9,15 @@ import numeral from '@elastic/numeral'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { EMPTY_STAT } from '../../../../helpers'; -import { alertIndexWithAllResults } from '../../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { auditbeatWithAllResults } from '../../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { packetbeatNoResults } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { EMPTY_STAT } from '../../constants'; +import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../../mock/test_providers/test_providers'; -import { PatternRollup } from '../../../../types'; +} from '../../mock/test_providers/test_providers'; +import { PatternRollup } from '../../types'; import { Props, StorageDetails } from '.'; const defaultBytesFormat = '0,0.[0]b'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/index.tsx similarity index 81% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/index.tsx index ff43116c02878..cfde7c0342585 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/index.tsx @@ -7,12 +7,12 @@ import React, { useCallback, useMemo } from 'react'; -import { useResultsRollupContext } from '../../../../contexts/results_rollup_context'; +import { useResultsRollupContext } from '../../contexts/results_rollup_context'; import { getFlattenedBuckets } from './helpers'; -import { StorageTreemap } from '../../../storage_treemap'; -import { DEFAULT_MAX_CHART_HEIGHT } from '../../../tabs/styles'; -import { SelectedIndex } from '../../../../types'; -import { useDataQualityContext } from '../../../data_quality_context'; +import { StorageTreemap } from './storage_treemap'; +import { DEFAULT_MAX_CHART_HEIGHT } from '../indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/styles'; +import { SelectedIndex } from '../../types'; +import { useDataQualityContext } from '../../data_quality_context'; import { DOCS_UNIT } from './translations'; export interface Props { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/chart_legend_item/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/chart_legend_item/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/chart_legend_item/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/chart_legend_item/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/index.test.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/index.test.tsx index 5e22bc185b1c1..a8998a134416f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/index.test.tsx @@ -11,24 +11,20 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { - FlattenedBucket, - getFlattenedBuckets, - getLegendItems, -} from '../body/data_quality_details/storage_details/helpers'; -import { EMPTY_STAT } from '../../helpers'; -import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { FlattenedBucket, getFlattenedBuckets, getLegendItems } from '../helpers'; +import { EMPTY_STAT } from '../../../constants'; +import { alertIndexWithAllResults } from '../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../mock/test_providers/test_providers'; +} from '../../../mock/test_providers/test_providers'; import type { Props } from '.'; import { StorageTreemap } from '.'; -import { DEFAULT_MAX_CHART_HEIGHT } from '../tabs/styles'; +import { DEFAULT_MAX_CHART_HEIGHT } from '../../indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/styles'; import { NO_DATA_LABEL } from './translations'; -import { PatternRollup } from '../../types'; +import { PatternRollup } from '../../../types'; const defaultBytesFormat = '0,0.[0]b'; const formatBytes = (value: number | undefined) => diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/index.tsx similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/index.tsx index e56cbadc66009..fbabe4412e493 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/index.tsx @@ -27,12 +27,15 @@ import { getLayersMultiDimensional, getLegendItems, getPathToFlattenedBucketMap, -} from '../body/data_quality_details/storage_details/helpers'; +} from '../helpers'; import { ChartLegendItem } from './chart_legend_item'; import { NoData } from './no_data'; -import { ChartFlexItem, LegendContainer } from '../tabs/styles'; -import { PatternRollup, SelectedIndex } from '../../types'; -import { useDataQualityContext } from '../data_quality_context'; +import { + ChartFlexItem, + LegendContainer, +} from '../../indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/styles'; +import { PatternRollup, SelectedIndex } from '../../../types'; +import { useDataQualityContext } from '../../../data_quality_context'; export const DEFAULT_MIN_CHART_HEIGHT = 240; // px export const LEGEND_WIDTH = 220; // px diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/no_data/index.test.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/no_data/index.test.tsx index 95503d7f156bd..dbb6dd955c9ea 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/no_data/index.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import * as i18n from '../translations'; import { NoData } from '.'; -import { TestExternalProviders } from '../../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../mock/test_providers/test_providers'; describe('NoData', () => { test('renders the expected "no data" message', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/no_data/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/no_data/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/no_data/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/storage_treemap/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/storage_treemap/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/body/data_quality_details/storage_details/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_details/storage_details/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/index.test.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/index.test.tsx index d2d0a388c465d..4a821d1251ddc 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/index.test.tsx @@ -13,7 +13,7 @@ import { IlmPhaseFilter } from '.'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; +} from '../../mock/test_providers/test_providers'; import { COLD_DESCRIPTION, FROZEN_DESCRIPTION, @@ -21,7 +21,7 @@ import { INDEX_LIFECYCLE_MANAGEMENT_PHASES, UNMANAGED_DESCRIPTION, WARM_DESCRIPTION, -} from '../../../translations'; +} from '../../translations'; describe('IlmPhaseFilter', () => { it('renders combobox with ilmPhase label and preselected hot, warm, unmanaged options', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/index.tsx similarity index 93% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/index.tsx index d0411349662fd..cd7148d38b3aa 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/index.tsx @@ -14,13 +14,13 @@ import { } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { ilmPhaseOptionsStatic } from '../../../constants'; -import { getIlmPhaseDescription } from '../../../helpers'; +import { ilmPhaseOptionsStatic } from '../../constants'; +import { getIlmPhaseDescription } from '../../utils/get_ilm_phase_description'; import { ILM_PHASE, INDEX_LIFECYCLE_MANAGEMENT_PHASES, SELECT_ONE_OR_MORE_ILM_PHASES, -} from '../../../translations'; +} from '../../translations'; import { useDataQualityContext } from '../../data_quality_context'; import { StyledFormControlLayout, StyledOption, StyledOptionLabel } from './styles'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/styles.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/ilm_phase_filter/styles.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/ilm_phase_filter/styles.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/index.test.tsx similarity index 86% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/index.test.tsx index 6b8994e0d7919..322d30be6dd81 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/index.test.tsx @@ -9,15 +9,15 @@ import numeral from '@elastic/numeral'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { EMPTY_STAT } from '../../helpers'; -import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { EMPTY_STAT } from '../constants'; +import { alertIndexWithAllResults } from '../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../mock/test_providers/test_providers'; -import { PatternRollup } from '../../types'; +} from '../mock/test_providers/test_providers'; +import { PatternRollup } from '../types'; import { DataQualitySummary } from '.'; import { getTotalDocsCount, @@ -25,7 +25,7 @@ import { getTotalIndices, getTotalIndicesChecked, getTotalSizeInBytes, -} from '../../use_results_rollup/helpers'; +} from '../hooks/use_results_rollup/utils/stats'; const defaultBytesFormat = '0,0.[0]b'; const formatBytes = (value: number | undefined) => diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/index.tsx similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/index.tsx index eca684ce5c218..09c98c1a84197 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/index.tsx @@ -9,11 +9,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { StatsRollup } from '../pattern/pattern_summary/stats_rollup'; +import { StatsRollup } from '../stats_rollup'; import { SummaryActions } from './summary_actions'; import { IlmPhaseFilter } from './ilm_phase_filter'; import { useDataQualityContext } from '../data_quality_context'; -import { useResultsRollupContext } from '../../contexts/results_rollup_context'; +import { useResultsRollupContext } from '../contexts/results_rollup_context'; const MAX_SUMMARY_ACTIONS_CONTAINER_WIDTH = 400; const MIN_SUMMARY_ACTIONS_CONTAINER_WIDTH = 235; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts index 5f96cfa9953a6..2b37622baa655 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.test.ts @@ -6,7 +6,7 @@ */ import { getAllIndicesToCheck, getIndexDocsCountFromRollup, getIndexToCheck } from './helpers'; -import { mockPacketbeatPatternRollup } from '../../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { mockPacketbeatPatternRollup } from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; const patternIndexNames: Record = { 'packetbeat-*': [ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts index ede3184350e58..dfbc0f69f82aa 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/helpers.ts @@ -7,8 +7,8 @@ import { orderBy } from 'lodash/fp'; -import { getDocsCount } from '../../../../helpers'; -import type { IndexToCheck, MeteringStatsIndex, PatternRollup } from '../../../../types'; +import type { IndexToCheck, MeteringStatsIndex, PatternRollup } from '../../../types'; +import { getDocsCount } from '../../../utils/stats'; export const getIndexToCheck = ({ indexName, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx index 97e93124b84cf..14368907362fb 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.test.tsx @@ -10,16 +10,16 @@ import userEvent from '@testing-library/user-event'; import { act, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; -import { mockMappingsResponse } from '../../../../mock/mappings_response/mock_mappings_response'; +import { mockMappingsResponse } from '../../../mock/mappings_response/mock_mappings_response'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../../mock/test_providers/test_providers'; -import { mockUnallowedValuesResponse } from '../../../../mock/unallowed_values/mock_unallowed_values'; -import { CANCEL, CHECK_ALL } from '../../../../translations'; -import { OnCheckCompleted, UnallowedValueRequestItem } from '../../../../types'; +} from '../../../mock/test_providers/test_providers'; +import { mockUnallowedValuesResponse } from '../../../mock/unallowed_values/mock_unallowed_values'; +import { CANCEL, CHECK_ALL } from '../../../translations'; +import { OnCheckCompleted, UnallowedValueRequestItem } from '../../../types'; import { CheckAll } from '.'; -import { EMPTY_STAT } from '../../../../helpers'; +import { EMPTY_STAT } from '../../../constants'; const defaultBytesFormat = '0,0.[0]b'; const mockFormatBytes = (value: number | undefined) => @@ -35,16 +35,19 @@ const mockFetchMappings = jest.fn(() => ) ); -jest.mock('../../../../use_mappings/helpers', () => ({ - fetchMappings: (_: { abortController: AbortController; patternOrIndexName: string }) => - mockFetchMappings(), -})); +jest.mock('../../../utils/fetch_mappings', () => { + const original = jest.requireActual('../../../utils/fetch_mappings'); + return { + ...original, + fetchMappings: (_: { abortController: AbortController; patternOrIndexName: string }) => + mockFetchMappings(), + }; +}); const mockFetchUnallowedValues = jest.fn(() => Promise.resolve(mockUnallowedValuesResponse)); -jest.mock('../../../../use_unallowed_values/helpers', () => { - const original = jest.requireActual('../../../../use_unallowed_values/helpers'); - +jest.mock('../../../utils/fetch_unallowed_values', () => { + const original = jest.requireActual('../../../utils/fetch_unallowed_values'); return { ...original, fetchUnallowedValues: (_: { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx index c8bb58de2ef44..e2851ee4b3761 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx @@ -10,12 +10,12 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { v4 as uuidv4 } from 'uuid'; -import { useResultsRollupContext } from '../../../../contexts/results_rollup_context'; -import { checkIndex } from '../../../../utils/check_index'; -import { useDataQualityContext } from '../../../data_quality_context'; import { getAllIndicesToCheck } from './helpers'; -import * as i18n from '../../../../translations'; -import type { IndexToCheck } from '../../../../types'; +import { useResultsRollupContext } from '../../../contexts/results_rollup_context'; +import { checkIndex } from '../../../utils/check_index'; +import { useDataQualityContext } from '../../../data_quality_context'; +import * as i18n from '../../../translations'; +import type { IndexToCheck } from '../../../types'; const CheckAllButton = styled(EuiButton)` width: 112px; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_all/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/helpers.test.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/helpers.test.tsx index 4ef8e57bf74b9..13b2e5e88605a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/helpers.test.tsx @@ -10,8 +10,8 @@ import { omit } from 'lodash/fp'; import React from 'react'; import { getErrorsViewerTableColumns } from './helpers'; -import { TestExternalProviders } from '../../../mock/test_providers/test_providers'; -import { ErrorSummary } from '../../../types'; +import { TestExternalProviders } from '../../../../../mock/test_providers/test_providers'; +import { ErrorSummary } from '../../../../../types'; const errorSummary: ErrorSummary[] = [ { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/helpers.tsx similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/helpers.tsx index 35a4a74cca875..a72580e464ad4 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/helpers.tsx @@ -10,7 +10,7 @@ import { EuiCode } from '@elastic/eui'; import React from 'react'; import * as i18n from './translations'; -import type { ErrorSummary } from '../../../types'; +import type { ErrorSummary } from '../../../../../types'; export const EMPTY_PLACEHOLDER = '--'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/index.test.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/index.test.tsx index 0354f687cda24..1c605a64d8ab2 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/index.test.tsx @@ -8,9 +8,9 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { TestExternalProviders } from '../../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../../mock/test_providers/test_providers'; import { ERROR, INDEX, PATTERN } from './translations'; -import { ErrorSummary } from '../../../types'; +import { ErrorSummary } from '../../../../../types'; import { ErrorsViewer } from '.'; interface ExpectedColumns { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/index.tsx similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/index.tsx index 2336abe79c651..363127e4f919c 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/index.tsx @@ -14,7 +14,7 @@ import { ERRORS_CONTAINER_MIN_WIDTH, getErrorsViewerTableColumns, } from './helpers'; -import type { ErrorSummary } from '../../../types'; +import type { ErrorSummary } from '../../../../../types'; const ErrorsViewerContainer = styled.div` max-width: ${ERRORS_CONTAINER_MAX_WIDTH}px; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_viewer/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/errors_viewer/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/index.test.tsx similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/index.test.tsx index ecedacc646043..43cd0b7d215c6 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/index.test.tsx @@ -9,7 +9,7 @@ import userEvent from '@testing-library/user-event'; import { act, render, screen } from '@testing-library/react'; import React from 'react'; -import { TestExternalProviders } from '../../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../../../../mock/test_providers/test_providers'; import { ErrorsPopover } from '.'; const mockCopyToClipboard = jest.fn((value) => true); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/index.tsx similarity index 89% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/index.tsx index 8f80e3fa3cab5..a4bb73147e97b 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/index.tsx @@ -16,15 +16,15 @@ import { import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { ErrorsViewer } from '../errors_viewer'; -import { ERRORS_CONTAINER_MAX_WIDTH } from '../errors_viewer/helpers'; +import { ErrorsViewer } from './errors_viewer'; +import { ERRORS_CONTAINER_MAX_WIDTH } from './errors_viewer/helpers'; import { getErrorsMarkdownTable, getErrorsMarkdownTableRows, -} from '../../index_properties/markdown/helpers'; +} from '../../../../data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers'; import * as i18n from './translations'; -import type { ErrorSummary } from '../../../types'; -import { ERROR, INDEX, PATTERN } from '../errors_viewer/translations'; +import type { ErrorSummary } from '../../../../types'; +import { ERROR, INDEX, PATTERN } from './errors_viewer/translations'; const CallOut = styled(EuiCallOut)` max-width: ${ERRORS_CONTAINER_MAX_WIDTH}px; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/errors_popover/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/errors_popover/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/index.test.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/index.test.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/index.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/index.tsx index a39c7bbf7cc7d..694bab1b97efe 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/check_status/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/check_status/index.tsx @@ -9,10 +9,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiText } from '@ela import React, { useEffect, useState } from 'react'; import moment from 'moment'; -import { ErrorsPopover } from '../errors_popover'; +import { ErrorsPopover } from './errors_popover'; import * as i18n from '../../../translations'; import type { ErrorSummary, IndexToCheck } from '../../../types'; -import { useDataQualityContext } from '../../data_quality_context'; +import { useDataQualityContext } from '../../../data_quality_context'; export const EMPTY_LAST_CHECKED_DATE = '--'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx similarity index 88% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx index d202bdf9b342d..1bcfc9ec12aa3 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/index.test.tsx @@ -10,15 +10,15 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { EMPTY_STAT } from '../../../helpers'; -import { alertIndexWithAllResults } from '../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; -import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { packetbeatNoResults } from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { EMPTY_STAT } from '../../constants'; +import { alertIndexWithAllResults } from '../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatNoResults } from '../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../mock/test_providers/test_providers'; -import { PatternRollup } from '../../../types'; +} from '../../mock/test_providers/test_providers'; +import { PatternRollup } from '../../types'; import { SummaryActions } from '.'; import { getTotalDocsCount, @@ -26,7 +26,7 @@ import { getTotalIndices, getTotalIndicesChecked, getTotalSizeInBytes, -} from '../../../use_results_rollup/helpers'; +} from '../../hooks/use_results_rollup/utils/stats'; const mockCopyToClipboard = jest.fn((value) => true); jest.mock('@elastic/eui', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/index.tsx similarity index 91% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/index.tsx index e0d7233f538d2..d0037032172c3 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/index.tsx @@ -11,9 +11,9 @@ import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { CheckAll } from './check_all'; -import { CheckStatus } from '../check_status'; -import { ERROR, INDEX, PATTERN } from '../errors_viewer/translations'; -import { ERRORS } from '../errors_popover/translations'; +import { CheckStatus } from './check_status'; +import { ERROR, INDEX, PATTERN } from './check_status/errors_popover/errors_viewer/translations'; +import { ERRORS } from './check_status/errors_popover/translations'; import { getDataQualitySummaryMarkdownComment, getErrorsMarkdownTable, @@ -21,13 +21,17 @@ import { getPatternSummaryMarkdownComment, getSummaryTableMarkdownHeader, getSummaryTableMarkdownRow, -} from '../../index_properties/markdown/helpers'; -import { defaultSort, getSummaryTableItems } from '../../pattern/helpers'; -import type { DataQualityCheckResult, IndexToCheck, PatternRollup } from '../../../types'; -import { getErrorSummaries, getSizeInBytes } from '../../../helpers'; +} from '../../data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers'; +import { + defaultSort, + getSummaryTableItems, +} from '../../data_quality_details/indices_details/pattern/helpers'; +import type { DataQualityCheckResult, IndexToCheck, PatternRollup } from '../../types'; import { useDataQualityContext } from '../../data_quality_context'; -import { useResultsRollupContext } from '../../../contexts/results_rollup_context'; +import { useResultsRollupContext } from '../../contexts/results_rollup_context'; import { Actions } from '../../actions'; +import { getErrorSummaries } from './utils/get_error_summaries'; +import { getSizeInBytes } from '../../utils/stats'; const StyledActionsContainerFlexItem = styled(EuiFlexItem)` margin-top: auto; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/utils/get_error_summaries.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/utils/get_error_summaries.test.ts new file mode 100644 index 0000000000000..c1e78b92c0fd8 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/utils/get_error_summaries.test.ts @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { alertIndexNoResults } from '../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { + auditbeatNoResults, + auditbeatWithAllResults, +} from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { + packetbeatNoResults, + packetbeatWithSomeErrors, +} from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { DataQualityCheckResult, PatternRollup } from '../../../types'; +import { + getErrorSummaries, + getErrorSummariesForRollup, + getErrorSummary, +} from './get_error_summaries'; + +describe('getErrorSummary', () => { + test('it returns the expected error summary', () => { + const resultWithError: DataQualityCheckResult = { + docsCount: 1630289, + error: + 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', + ilmPhase: 'hot', + incompatible: undefined, + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'packetbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }; + + expect(getErrorSummary(resultWithError)).toEqual({ + error: + 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + }); + }); +}); + +describe('getErrorSummariesForRollup', () => { + test('it returns the expected array of `ErrorSummary` when the `PatternRollup` contains errors', () => { + expect(getErrorSummariesForRollup(packetbeatWithSomeErrors)).toEqual([ + { + error: + 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + }, + ]); + }); + + test('it returns the an empty array of `ErrorSummary` when the `PatternRollup` contains all results, with NO errors', () => { + expect(getErrorSummariesForRollup(auditbeatWithAllResults)).toEqual([]); + }); + + test('it returns the an empty array of `ErrorSummary` when the `PatternRollup` has NO results', () => { + expect(getErrorSummariesForRollup(auditbeatNoResults)).toEqual([]); + }); + + test('it returns the an empty array of `ErrorSummary` when the `PatternRollup` is undefined', () => { + expect(getErrorSummariesForRollup(undefined)).toEqual([]); + }); + + test('it returns BOTH the expected (root) pattern-level error, and an index-level error when `PatternRollup` has both', () => { + const withPatternLevelError: PatternRollup = { + ...packetbeatWithSomeErrors, + error: 'This is a pattern-level error', + }; + + expect(getErrorSummariesForRollup(withPatternLevelError)).toEqual([ + { + error: 'This is a pattern-level error', + indexName: null, + pattern: 'packetbeat-*', + }, + { + error: + 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + }, + ]); + }); + + test('it returns the expected (root) pattern-level error when there are no index-level results', () => { + const withPatternLevelError: PatternRollup = { + ...auditbeatNoResults, + error: 'This is a pattern-level error', + }; + + expect(getErrorSummariesForRollup(withPatternLevelError)).toEqual([ + { + error: 'This is a pattern-level error', + indexName: null, + pattern: 'auditbeat-*', + }, + ]); + }); +}); + +describe('getErrorSummaries', () => { + test('it returns an empty array when patternRollups is empty', () => { + expect(getErrorSummaries({})).toEqual([]); + }); + + test('it returns an empty array when none of the patternRollups have errors', () => { + expect( + getErrorSummaries({ + '.alerts-security.alerts-default': alertIndexNoResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, + }) + ).toEqual([]); + }); + + test('it returns the expected array of `ErrorSummary` when some of the `PatternRollup` contain errors', () => { + expect( + getErrorSummaries({ + '.alerts-security.alerts-default': alertIndexNoResults, + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatWithSomeErrors, // <-- has errors + }) + ).toEqual([ + { + error: + 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + }, + ]); + }); + + test('it returns the expected array of `ErrorSummary` when there are both pattern-level and index-level errors', () => { + const withPatternLevelError: PatternRollup = { + ...auditbeatNoResults, + error: 'This is a pattern-level error', + }; + + expect( + getErrorSummaries({ + '.alerts-security.alerts-default': alertIndexNoResults, + 'auditbeat-*': withPatternLevelError, // <-- has pattern-level errors + 'packetbeat-*': packetbeatWithSomeErrors, // <-- has index-level errors + }) + ).toEqual([ + { + error: 'This is a pattern-level error', + indexName: null, + pattern: 'auditbeat-*', + }, + { + error: + 'Error loading mappings for .ds-packetbeat-8.5.3-2023.02.04-000001: Error: simulated error fetching index .ds-packetbeat-8.5.3-2023.02.04-000001', + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + pattern: 'packetbeat-*', + }, + ]); + }); + + test('it returns the expected array of `ErrorSummary` when there are just pattern-level errors', () => { + const withPatternLevelError: PatternRollup = { + ...auditbeatNoResults, + error: 'This is a pattern-level error', + }; + + expect( + getErrorSummaries({ + '.alerts-security.alerts-default': alertIndexNoResults, + 'auditbeat-*': withPatternLevelError, // <-- has pattern-level errors + 'packetbeat-*': packetbeatNoResults, + }) + ).toEqual([ + { + error: 'This is a pattern-level error', + indexName: null, + pattern: 'auditbeat-*', + }, + ]); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/utils/get_error_summaries.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/utils/get_error_summaries.ts new file mode 100644 index 0000000000000..a3b6b8ac7d1d8 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/data_quality_summary/summary_actions/utils/get_error_summaries.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import sortBy from 'lodash/fp/sortBy'; +import { DataQualityCheckResult, ErrorSummary, PatternRollup } from '../../../types'; + +export const getErrorSummary = ({ + error, + indexName, + pattern, +}: DataQualityCheckResult): ErrorSummary => ({ + error: String(error), + indexName, + pattern, +}); + +export const getErrorSummariesForRollup = ( + patternRollup: PatternRollup | undefined +): ErrorSummary[] => { + const maybePatternErrorSummary: ErrorSummary[] = + patternRollup != null && patternRollup.error != null + ? [{ pattern: patternRollup.pattern, indexName: null, error: patternRollup.error }] + : []; + + if (patternRollup != null && patternRollup.results != null) { + const unsortedResults: DataQualityCheckResult[] = Object.values(patternRollup.results); + const sortedResults = sortBy('indexName', unsortedResults); + + return sortedResults.reduce( + (acc, result) => [...acc, ...(result.error != null ? [getErrorSummary(result)] : [])], + maybePatternErrorSummary + ); + } else { + return maybePatternErrorSummary; + } +}; + +export const getErrorSummaries = ( + patternRollups: Record +): ErrorSummary[] => { + const allPatterns: string[] = Object.keys(patternRollups); + + // sort the patterns A-Z: + const sortedPatterns = [...allPatterns].sort((a, b) => { + return a.localeCompare(b); + }); + + return sortedPatterns.reduce( + (acc, pattern) => [...acc, ...getErrorSummariesForRollup(patternRollups[pattern])], + [] + ); +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/index.test.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/index.test.tsx index b129102e5516e..f16803936794d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/index.test.tsx @@ -9,14 +9,14 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useIndicesCheck } from '.'; -import * as utilsCheckIndex from '../utils/check_index'; -import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values'; -import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_response'; +import * as utilsCheckIndex from '../../utils/check_index'; +import { mockUnallowedValuesResponse } from '../../mock/unallowed_values/mock_unallowed_values'; +import { mockMappingsResponse } from '../../mock/mappings_response/mock_mappings_response'; import { HttpHandler } from '@kbn/core-http-browser'; -import { MappingsError } from '../use_mappings/helpers'; -import { UnallowedValuesError } from '../use_unallowed_values/helpers'; +import { MappingsError } from '../../utils/fetch_mappings'; +import { UnallowedValuesError } from '../../utils/fetch_unallowed_values'; import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; -import { UnallowedValueSearchResult } from '../types'; +import { UnallowedValueSearchResult } from '../../types'; import { getInitialCheckStateValue } from './reducer'; const getSpies = () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/index.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/index.tsx index acbad56a613c5..6ad00aacb338d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/index.tsx @@ -6,10 +6,10 @@ */ import { useReducer, useCallback } from 'react'; -import { OnCheckCompleted } from '../types'; -import { MappingsError } from '../use_mappings/helpers'; -import { UnallowedValuesError } from '../use_unallowed_values/helpers'; -import { checkIndex as _checkIndex, CheckIndexProps } from '../utils/check_index'; +import { OnCheckCompleted } from '../../types'; +import { MappingsError } from '../../utils/fetch_mappings'; +import { UnallowedValuesError } from '../../utils/fetch_unallowed_values'; +import { checkIndex as _checkIndex, CheckIndexProps } from '../../utils/check_index'; import { initialState, reducer } from './reducer'; import { UseIndicesCheckReturnValue } from './types'; import { useIsMounted } from '../use_is_mounted'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/reducer.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/reducer.ts similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/reducer.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/reducer.ts index ca596643605ce..73bb4c1a98df1 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/reducer.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/reducer.ts @@ -14,9 +14,9 @@ import { PartitionedFieldMetadata, UnallowedValueCount, UnallowedValueSearchResult, -} from '../types'; -import { MappingsError } from '../use_mappings/helpers'; -import { UnallowedValuesError } from '../use_unallowed_values/helpers'; +} from '../../types'; +import { MappingsError } from '../../utils/fetch_mappings'; +import { UnallowedValuesError } from '../../utils/fetch_unallowed_values'; import { UseIndicesCheckState } from './types'; type Action = { data: { indexName: string } } & ( diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/types.ts similarity index 86% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/types.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/types.ts index c7f0145f68f15..2b9ecf147e0a3 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_indices_check/types.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_indices_check/types.ts @@ -13,10 +13,10 @@ import { PartitionedFieldMetadata, UnallowedValueCount, UnallowedValueSearchResult, -} from '../types'; -import { MappingsError } from '../use_mappings/helpers'; -import { UnallowedValuesError } from '../use_unallowed_values/helpers'; -import { CheckIndexProps } from '../utils/check_index'; +} from '../../types'; +import { MappingsError } from '../../utils/fetch_mappings'; +import { UnallowedValuesError } from '../../utils/fetch_unallowed_values'; +import { CheckIndexProps } from '../../utils/check_index'; export interface UseIndicesCheckCheckState { [indexName: string]: { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_is_mounted/index.test.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_is_mounted/index.test.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_is_mounted/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_is_mounted/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_is_mounted/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/constants.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/constants.ts new file mode 100644 index 0000000000000..e2251d526136f --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const POST_INDEX_RESULTS = '/internal/ecs_data_quality_dashboard/results'; +export const GET_INDEX_RESULTS_LATEST = + '/internal/ecs_data_quality_dashboard/results_latest/{pattern}'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.tsx similarity index 93% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.tsx index f0c6f9d28e501..eb8cd670d70c5 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/index.tsx @@ -7,10 +7,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { EcsVersion } from '@elastic/ecs'; - import { isEmpty } from 'lodash/fp'; import { IToasts } from '@kbn/core-notifications-browser'; import { HttpHandler } from '@kbn/core-http-browser'; + import { getTotalDocsCount, getTotalIncompatible, @@ -18,33 +18,34 @@ import { getTotalIndicesChecked, getTotalSameFamily, getTotalSizeInBytes, - updateResultOnCheckCompleted, -} from './helpers'; - + getTotalPatternSameFamily, + getIndexId, +} from './utils/stats'; +import { + getStorageResults, + postStorageResult, + formatStorageResult, + formatResultFromStorage, +} from './utils/storage'; +import { getPatternRollupsWithLatestCheckResult } from './utils/get_pattern_rollups_with_latest_check_result'; import type { DataQualityCheckResult, OnCheckCompleted, PatternRollup, TelemetryEvents, -} from '../types'; +} from '../../types'; import { - getDocsCount, - getIndexId, - getStorageResults, - getSizeInBytes, - getTotalPatternSameFamily, - postStorageResult, - formatStorageResult, - formatResultFromStorage, -} from '../helpers'; -import { getIlmPhase, getIndexIncompatible } from '../data_quality_panel/pattern/helpers'; + getIlmPhase, + getIndexIncompatible, +} from '../../data_quality_details/indices_details/pattern/helpers'; import { getIncompatibleMappingsFields, getIncompatibleValuesFields, getSameFamilyFields, -} from '../data_quality_panel/tabs/incompatible_tab/helpers'; +} from '../../data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers'; import { UseResultsRollupReturnValue } from './types'; import { useIsMounted } from '../use_is_mounted'; +import { getDocsCount, getSizeInBytes } from '../../utils/stats'; interface Props { ilmPhases: string[]; @@ -169,7 +170,7 @@ export const useResultsRollup = ({ isCheckAll, }) => { setPatternRollups((currentPatternRollups) => { - const updatedRollups = updateResultOnCheckCompleted({ + const updatedRollups = getPatternRollupsWithLatestCheckResult({ error, formatBytes, formatNumber, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/types.ts new file mode 100644 index 0000000000000..093ee8fdd645c --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/types.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + IlmPhase, + IncompatibleFieldMappingItem, + IncompatibleFieldValueItem, + OnCheckCompleted, + PatternRollup, + SameFamilyFieldItem, +} from '../../types'; + +export interface UseResultsRollupReturnValue { + onCheckCompleted: OnCheckCompleted; + patternIndexNames: Record; + patternRollups: Record; + totalDocsCount: number | undefined; + totalIncompatible: number | undefined; + totalIndices: number | undefined; + totalIndicesChecked: number | undefined; + totalSameFamily: number | undefined; + totalSizeInBytes: number | undefined; + updatePatternIndexNames: ({ + indexNames, + pattern, + }: { + indexNames: string[]; + pattern: string; + }) => void; + updatePatternRollup: (patternRollup: PatternRollup) => void; +} + +export interface StorageResult { + batchId: string; + indexName: string; + indexPattern: string; + isCheckAll: boolean; + checkedAt: number; + docsCount: number; + totalFieldCount: number; + ecsFieldCount: number; + customFieldCount: number; + incompatibleFieldCount: number; + incompatibleFieldMappingItems: IncompatibleFieldMappingItem[]; + incompatibleFieldValueItems: IncompatibleFieldValueItem[]; + sameFamilyFieldCount: number; + sameFamilyFields: string[]; + sameFamilyFieldItems: SameFamilyFieldItem[]; + unallowedMappingFields: string[]; + unallowedValueFields: string[]; + sizeInBytes: number; + ilmPhase?: IlmPhase; + markdownComments: string[]; + ecsVersion: string; + indexId: string; + error: string | null; +} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/get_pattern_rollups_with_latest_check_result.test.ts similarity index 77% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/get_pattern_rollups_with_latest_check_result.test.ts index c724ee1ae38de..f6cd702ef139f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/get_pattern_rollups_with_latest_check_result.test.ts @@ -7,24 +7,11 @@ import numeral from '@elastic/numeral'; -import { - getTotalDocsCount, - getTotalIncompatible, - getTotalIndices, - getTotalIndicesChecked, - getTotalSameFamily, - updateResultOnCheckCompleted, -} from './helpers'; -import { auditbeatWithAllResults } from '../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; -import { - mockPacketbeatPatternRollup, - packetbeatNoResults, - packetbeatWithSomeErrors, -} from '../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; -import { DataQualityCheckResult, MeteringStatsIndex, PatternRollup } from '../types'; -import { EMPTY_STAT } from '../helpers'; -import { mockPartitionedFieldMetadata } from '../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; -import { alertIndexWithAllResults } from '../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { getPatternRollupsWithLatestCheckResult } from './get_pattern_rollups_with_latest_check_result'; +import { mockPacketbeatPatternRollup } from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { MeteringStatsIndex, PatternRollup } from '../../../types'; +import { EMPTY_STAT } from '../../../constants'; +import { mockPartitionedFieldMetadata } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; import { EcsVersion } from '@elastic/ecs'; const defaultBytesFormat = '0,0.[0]b'; @@ -35,11 +22,6 @@ const defaultNumberFormat = '0,0.[000]'; const formatNumber = (value: number | undefined) => value != null ? numeral(value).format(defaultNumberFormat) : EMPTY_STAT; -const patternRollups: Record = { - 'auditbeat-*': auditbeatWithAllResults, // indices: 3 - 'packetbeat-*': mockPacketbeatPatternRollup, // indices: 2 -}; - describe('helpers', () => { let originalFetch: (typeof global)['fetch']; @@ -51,121 +33,6 @@ describe('helpers', () => { global.fetch = originalFetch; }); - describe('getTotalSameFamily', () => { - const defaultDataQualityCheckResult: DataQualityCheckResult = { - docsCount: 26093, - error: null, - ilmPhase: 'hot', - incompatible: 0, - indexName: '.internal.alerts-security.alerts-default-000001', - markdownComments: ['foo', 'bar', 'baz'], - pattern: '.alerts-security.alerts-default', - sameFamily: 7, - checkedAt: 1706526408000, - }; - - const alertIndexWithSameFamily: PatternRollup = { - ...alertIndexWithAllResults, - results: { - '.internal.alerts-security.alerts-default-000001': { - ...defaultDataQualityCheckResult, - }, - }, - }; - - const withSameFamily: Record = { - '.internal.alerts-security.alerts-default-000001': alertIndexWithSameFamily, - }; - - test('it returns the expected count when patternRollups has sameFamily', () => { - expect(getTotalSameFamily(withSameFamily)).toEqual(7); - }); - - test('it returns undefined when patternRollups is empty', () => { - expect(getTotalSameFamily({})).toBeUndefined(); - }); - - test('it returns zero when none of the rollups have same family', () => { - expect(getTotalSameFamily(patternRollups)).toEqual(0); - }); - }); - - describe('getTotalIndices', () => { - test('it returns the expected total when ALL `PatternRollup`s have an `indices`', () => { - expect(getTotalIndices(patternRollups)).toEqual(5); - }); - - test('it returns undefined when only SOME of the `PatternRollup`s have an `indices`', () => { - const someIndicesAreUndefined: Record = { - 'auditbeat-*': { - ...auditbeatWithAllResults, - indices: undefined, // <-- - }, - 'packetbeat-*': mockPacketbeatPatternRollup, // indices: 2 - }; - - expect(getTotalIndices(someIndicesAreUndefined)).toBeUndefined(); - }); - }); - - describe('getTotalDocsCount', () => { - test('it returns the expected total when ALL `PatternRollup`s have a `docsCount`', () => { - expect(getTotalDocsCount(patternRollups)).toEqual( - Number(auditbeatWithAllResults.docsCount) + Number(mockPacketbeatPatternRollup.docsCount) - ); - }); - - test('it returns undefined when only SOME of the `PatternRollup`s have a `docsCount`', () => { - const someIndicesAreUndefined: Record = { - 'auditbeat-*': { - ...auditbeatWithAllResults, - docsCount: undefined, // <-- - }, - 'packetbeat-*': mockPacketbeatPatternRollup, - }; - - expect(getTotalDocsCount(someIndicesAreUndefined)).toBeUndefined(); - }); - }); - - describe('getTotalIncompatible', () => { - test('it returns the expected total when ALL `PatternRollup`s have `results`', () => { - expect(getTotalIncompatible(patternRollups)).toEqual(4); - }); - - test('it returns the expected total when only SOME of the `PatternRollup`s have `results`', () => { - const someResultsAreUndefined: Record = { - 'auditbeat-*': auditbeatWithAllResults, - 'packetbeat-*': packetbeatNoResults, // <-- results is undefined - }; - - expect(getTotalIncompatible(someResultsAreUndefined)).toEqual(4); - }); - - test('it returns undefined when NONE of the `PatternRollup`s have `results`', () => { - const someResultsAreUndefined: Record = { - 'packetbeat-*': packetbeatNoResults, // <-- results is undefined - }; - - expect(getTotalIncompatible(someResultsAreUndefined)).toBeUndefined(); - }); - }); - - describe('getTotalIndicesChecked', () => { - test('it returns the expected total', () => { - expect(getTotalIndicesChecked(patternRollups)).toEqual(3); - }); - - test('it returns the expected total when errors have occurred', () => { - const someErrors: Record = { - 'auditbeat-*': auditbeatWithAllResults, // indices: 3 - 'packetbeat-*': packetbeatWithSomeErrors, // <-- indices: 2, but one has errors - }; - - expect(getTotalIndicesChecked(someErrors)).toEqual(4); - }); - }); - describe('updateResultOnCheckCompleted', () => { const packetbeatStats861: MeteringStatsIndex = mockPacketbeatPatternRollup.stats != null @@ -178,7 +45,7 @@ describe('helpers', () => { test('it returns the updated rollups', () => { expect( - updateResultOnCheckCompleted({ + getPatternRollupsWithLatestCheckResult({ error: null, formatBytes, formatNumber, @@ -280,7 +147,7 @@ describe('helpers', () => { }; expect( - updateResultOnCheckCompleted({ + getPatternRollupsWithLatestCheckResult({ error: null, formatBytes, formatNumber, @@ -377,7 +244,7 @@ describe('helpers', () => { test('it returns the expected results when `partitionedFieldMetadata` is null', () => { expect( - updateResultOnCheckCompleted({ + getPatternRollupsWithLatestCheckResult({ error: null, formatBytes, formatNumber, @@ -472,7 +339,7 @@ describe('helpers', () => { }; expect( - updateResultOnCheckCompleted({ + getPatternRollupsWithLatestCheckResult({ error: null, formatBytes, formatNumber, @@ -532,7 +399,7 @@ describe('helpers', () => { }; expect( - updateResultOnCheckCompleted({ + getPatternRollupsWithLatestCheckResult({ error: null, formatBytes, formatNumber, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/get_pattern_rollups_with_latest_check_result.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/get_pattern_rollups_with_latest_check_result.ts new file mode 100644 index 0000000000000..22b7eb6db4d8d --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/get_pattern_rollups_with_latest_check_result.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getIndexDocsCountFromRollup } from '../../../data_quality_summary/summary_actions/check_all/helpers'; +import { getIlmPhase } from '../../../data_quality_details/indices_details/pattern/helpers'; +import { getAllIncompatibleMarkdownComments } from '../../../data_quality_details/indices_details/pattern/index_check_flyout/index_properties/index_check_fields/tabs/incompatible_tab/helpers'; +import { getSizeInBytes } from '../../../utils/stats'; +import type { IlmPhase, PartitionedFieldMetadata, PatternRollup } from '../../../types'; + +export const getPatternRollupsWithLatestCheckResult = ({ + error, + formatBytes, + formatNumber, + indexName, + isILMAvailable, + partitionedFieldMetadata, + pattern, + patternRollups, +}: { + error: string | null; + formatBytes: (value: number | undefined) => string; + formatNumber: (value: number | undefined) => string; + indexName: string; + isILMAvailable: boolean; + partitionedFieldMetadata: PartitionedFieldMetadata | null; + pattern: string; + patternRollups: Record; +}): Record => { + const patternRollup: PatternRollup | undefined = patternRollups[pattern]; + + if (patternRollup != null) { + const ilmExplain = patternRollup.ilmExplain; + + const ilmPhase: IlmPhase | undefined = + ilmExplain != null ? getIlmPhase(ilmExplain[indexName], isILMAvailable) : undefined; + + const docsCount = getIndexDocsCountFromRollup({ + indexName, + patternRollup, + }); + + const patternDocsCount = patternRollup.docsCount ?? 0; + + const sizeInBytes = getSizeInBytes({ indexName, stats: patternRollup.stats }); + + const markdownComments = + partitionedFieldMetadata != null + ? getAllIncompatibleMarkdownComments({ + docsCount, + formatBytes, + formatNumber, + ilmPhase, + indexName, + isILMAvailable, + partitionedFieldMetadata, + patternDocsCount, + sizeInBytes, + }) + : []; + + const incompatible = partitionedFieldMetadata?.incompatible.length; + const sameFamily = partitionedFieldMetadata?.sameFamily.length; + const checkedAt = partitionedFieldMetadata ? Date.now() : undefined; + + return { + ...patternRollups, + [pattern]: { + ...patternRollup, + results: { + ...(patternRollup.results ?? {}), + [indexName]: { + docsCount, + error, + ilmPhase, + incompatible, + indexName, + markdownComments, + pattern, + sameFamily, + checkedAt, + }, + }, + }, + }; + } else { + return patternRollups; + } +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/stats.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/stats.test.ts new file mode 100644 index 0000000000000..7f28c6bcd1727 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/stats.test.ts @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { alertIndexWithAllResults } from '../../../mock/pattern_rollup/mock_alerts_pattern_rollup'; +import { auditbeatWithAllResults } from '../../../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { + mockPacketbeatPatternRollup, + packetbeatNoResults, + packetbeatWithSomeErrors, +} from '../../../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { mockStats } from '../../../mock/stats/mock_stats'; +import { DataQualityCheckResult, PatternRollup } from '../../../types'; +import { + getIndexId, + getTotalDocsCount, + getTotalIncompatible, + getTotalIndices, + getTotalIndicesChecked, + getTotalPatternSameFamily, + getTotalSameFamily, +} from './stats'; + +const patternRollups: Record = { + 'auditbeat-*': auditbeatWithAllResults, // indices: 3 + 'packetbeat-*': mockPacketbeatPatternRollup, // indices: 2 +}; + +describe('getTotalPatternSameFamily', () => { + const baseResult: DataQualityCheckResult = { + docsCount: 4, + error: null, + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + markdownComments: [ + '### auditbeat-custom-index-1\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase |\n|--------|-------|------|---------------------|-----------|\n| ❌ | auditbeat-custom-index-1 | 4 (0.0%) | 3 | `unmanaged` |\n\n', + '### **Incompatible fields** `3` **Custom fields** `4` **ECS compliant fields** `2` **All fields** `9`\n', + "#### 3 incompatible fields, 0 fields with mappings in the same family\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\nIncompatible fields with mappings in the same family have exactly the same search behavior but may have different space usage or performance characteristics.\n\nWhen an incompatible field is not in the same family:\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n#### Incompatible field mappings - auditbeat-custom-index-1\n\n\n| Field | ECS mapping type (expected) | Index mapping type (actual) | \n|-------|-----------------------------|-----------------------------|\n| host.name | `keyword` | `text` |\n| source.ip | `ip` | `text` |\n\n#### Incompatible field values - auditbeat-custom-index-1\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `an_invalid_category` (2),\n`theory` (1) |\n\n', + ], + pattern: 'auditbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }; + + it('returns undefined when results is undefined', () => { + expect(getTotalPatternSameFamily(undefined)).toBeUndefined(); + }); + + it('returns 0 when results is an empty object', () => { + expect(getTotalPatternSameFamily({})).toBe(0); + }); + + it('should sum sameFamily values and return the total', () => { + const results: Record = { + a: { + ...baseResult, + indexName: 'a', + markdownComments: [], + pattern: 'pattern', + sameFamily: 2, + }, + b: { + ...baseResult, + indexName: 'b', + markdownComments: [], + pattern: 'pattern', + sameFamily: 3, + }, + c: { ...baseResult, indexName: 'c', markdownComments: [], pattern: 'pattern' }, + }; + + expect(getTotalPatternSameFamily(results)).toBe(5); + }); + + it('handles a mix of defined and undefined sameFamily values', () => { + const results: Record = { + a: { + ...baseResult, + indexName: 'a', + markdownComments: [], + pattern: 'pattern', + sameFamily: 1, + }, + b: { + ...baseResult, + indexName: 'b', + markdownComments: [], + pattern: 'pattern', + sameFamily: undefined, + }, + c: { + ...baseResult, + indexName: 'c', + markdownComments: [], + pattern: 'pattern', + sameFamily: 2, + }, + }; + + expect(getTotalPatternSameFamily(results)).toBe(3); + }); +}); + +describe('getTotalSameFamily', () => { + const defaultDataQualityCheckResult: DataQualityCheckResult = { + docsCount: 26093, + error: null, + ilmPhase: 'hot', + incompatible: 0, + indexName: '.internal.alerts-security.alerts-default-000001', + markdownComments: ['foo', 'bar', 'baz'], + pattern: '.alerts-security.alerts-default', + sameFamily: 7, + checkedAt: 1706526408000, + }; + + const alertIndexWithSameFamily: PatternRollup = { + ...alertIndexWithAllResults, + results: { + '.internal.alerts-security.alerts-default-000001': { + ...defaultDataQualityCheckResult, + }, + }, + }; + + const withSameFamily: Record = { + '.internal.alerts-security.alerts-default-000001': alertIndexWithSameFamily, + }; + + test('it returns the expected count when patternRollups has sameFamily', () => { + expect(getTotalSameFamily(withSameFamily)).toEqual(7); + }); + + test('it returns undefined when patternRollups is empty', () => { + expect(getTotalSameFamily({})).toBeUndefined(); + }); + + test('it returns zero when none of the rollups have same family', () => { + expect(getTotalSameFamily(patternRollups)).toEqual(0); + }); +}); + +describe('getTotalIndices', () => { + test('it returns the expected total when ALL `PatternRollup`s have an `indices`', () => { + expect(getTotalIndices(patternRollups)).toEqual(5); + }); + + test('it returns undefined when only SOME of the `PatternRollup`s have an `indices`', () => { + const someIndicesAreUndefined: Record = { + 'auditbeat-*': { + ...auditbeatWithAllResults, + indices: undefined, // <-- + }, + 'packetbeat-*': mockPacketbeatPatternRollup, // indices: 2 + }; + + expect(getTotalIndices(someIndicesAreUndefined)).toBeUndefined(); + }); +}); + +describe('getTotalDocsCount', () => { + test('it returns the expected total when ALL `PatternRollup`s have a `docsCount`', () => { + expect(getTotalDocsCount(patternRollups)).toEqual( + Number(auditbeatWithAllResults.docsCount) + Number(mockPacketbeatPatternRollup.docsCount) + ); + }); + + test('it returns undefined when only SOME of the `PatternRollup`s have a `docsCount`', () => { + const someIndicesAreUndefined: Record = { + 'auditbeat-*': { + ...auditbeatWithAllResults, + docsCount: undefined, // <-- + }, + 'packetbeat-*': mockPacketbeatPatternRollup, + }; + + expect(getTotalDocsCount(someIndicesAreUndefined)).toBeUndefined(); + }); +}); + +describe('getTotalIncompatible', () => { + test('it returns the expected total when ALL `PatternRollup`s have `results`', () => { + expect(getTotalIncompatible(patternRollups)).toEqual(4); + }); + + test('it returns the expected total when only SOME of the `PatternRollup`s have `results`', () => { + const someResultsAreUndefined: Record = { + 'auditbeat-*': auditbeatWithAllResults, + 'packetbeat-*': packetbeatNoResults, // <-- results is undefined + }; + + expect(getTotalIncompatible(someResultsAreUndefined)).toEqual(4); + }); + + test('it returns undefined when NONE of the `PatternRollup`s have `results`', () => { + const someResultsAreUndefined: Record = { + 'packetbeat-*': packetbeatNoResults, // <-- results is undefined + }; + + expect(getTotalIncompatible(someResultsAreUndefined)).toBeUndefined(); + }); +}); + +describe('getTotalIndicesChecked', () => { + test('it returns the expected total', () => { + expect(getTotalIndicesChecked(patternRollups)).toEqual(3); + }); + + test('it returns the expected total when errors have occurred', () => { + const someErrors: Record = { + 'auditbeat-*': auditbeatWithAllResults, // indices: 3 + 'packetbeat-*': packetbeatWithSomeErrors, // <-- indices: 2, but one has errors + }; + + expect(getTotalIndicesChecked(someErrors)).toEqual(4); + }); +}); + +describe('getIndexId', () => { + it('returns the expected index ID', () => { + expect(getIndexId({ indexName: 'auditbeat-custom-index-1', stats: mockStats })).toEqual( + 'uyJDDqGrRQqdBTN0mCF-iw' + ); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/stats.ts similarity index 52% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/stats.ts index 07f51572b6ba2..c3a44f04471ea 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/stats.ts @@ -5,16 +5,20 @@ * 2.0. */ -import { getIndexDocsCountFromRollup } from '../data_quality_panel/data_quality_summary/summary_actions/check_all/helpers'; -import { getIlmPhase } from '../data_quality_panel/pattern/helpers'; -import { getAllIncompatibleMarkdownComments } from '../data_quality_panel/tabs/incompatible_tab/helpers'; -import { - getSizeInBytes, - getTotalPatternIncompatible, - getTotalPatternIndicesChecked, - getTotalPatternSameFamily, -} from '../helpers'; -import type { IlmPhase, PartitionedFieldMetadata, PatternRollup } from '../types'; +import { DataQualityCheckResult, MeteringStatsIndex, PatternRollup } from '../../../types'; +import { getTotalPatternIncompatible, getTotalPatternIndicesChecked } from '../../../utils/stats'; + +export const getTotalPatternSameFamily = ( + results: Record | undefined +): number | undefined => { + if (results == null) { + return undefined; + } + + const allResults = Object.values(results); + + return allResults.reduce((acc, { sameFamily }) => acc + (sameFamily ?? 0), 0); +}; export const getTotalIndices = ( patternRollups: Record @@ -87,82 +91,10 @@ export const getTotalIndicesChecked = (patternRollups: Record string; - formatNumber: (value: number | undefined) => string; indexName: string; - isILMAvailable: boolean; - partitionedFieldMetadata: PartitionedFieldMetadata | null; - pattern: string; - patternRollups: Record; -}): Record => { - const patternRollup: PatternRollup | undefined = patternRollups[pattern]; - - if (patternRollup != null) { - const ilmExplain = patternRollup.ilmExplain; - - const ilmPhase: IlmPhase | undefined = - ilmExplain != null ? getIlmPhase(ilmExplain[indexName], isILMAvailable) : undefined; - - const docsCount = getIndexDocsCountFromRollup({ - indexName, - patternRollup, - }); - - const patternDocsCount = patternRollup.docsCount ?? 0; - - const sizeInBytes = getSizeInBytes({ indexName, stats: patternRollup.stats }); - - const markdownComments = - partitionedFieldMetadata != null - ? getAllIncompatibleMarkdownComments({ - docsCount, - formatBytes, - formatNumber, - ilmPhase, - indexName, - isILMAvailable, - partitionedFieldMetadata, - patternDocsCount, - sizeInBytes, - }) - : []; - - const incompatible = partitionedFieldMetadata?.incompatible.length; - const sameFamily = partitionedFieldMetadata?.sameFamily.length; - const checkedAt = partitionedFieldMetadata ? Date.now() : undefined; - - return { - ...patternRollups, - [pattern]: { - ...patternRollup, - results: { - ...(patternRollup.results ?? {}), - [indexName]: { - docsCount, - error, - ilmPhase, - incompatible, - indexName, - markdownComments, - pattern, - sameFamily, - checkedAt, - }, - }, - }, - }; - } else { - return patternRollups; - } -}; + stats: Record | null; +}): string | null | undefined => stats && stats[indexName]?.uuid; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.test.ts new file mode 100644 index 0000000000000..0e894694ed1e9 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.test.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { mockPartitionedFieldMetadataWithSameFamily } from '../../../mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family'; +import { StorageResult } from '../types'; +import { formatStorageResult, getStorageResults, postStorageResult } from './storage'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; + +describe('formatStorageResult', () => { + it('should correctly format the input data into a StorageResult object', () => { + const inputData: Parameters[number] = { + result: { + indexName: 'testIndex', + pattern: 'testPattern', + checkedAt: 1627545600000, + docsCount: 100, + incompatible: 3, + sameFamily: 1, + ilmPhase: 'hot', + markdownComments: ['test comments'], + error: null, + }, + report: { + batchId: 'testBatch', + isCheckAll: true, + sameFamilyFields: ['agent.type'], + unallowedMappingFields: ['event.category', 'host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + sizeInBytes: 5000, + ecsVersion: '1.0.0', + indexName: 'testIndex', + indexId: 'testIndexId', + }, + partitionedFieldMetadata: mockPartitionedFieldMetadataWithSameFamily, + }; + + const expectedResult: StorageResult = { + batchId: 'testBatch', + indexName: 'testIndex', + indexPattern: 'testPattern', + isCheckAll: true, + checkedAt: 1627545600000, + docsCount: 100, + totalFieldCount: 10, + ecsFieldCount: 2, + customFieldCount: 4, + incompatibleFieldCount: 3, + incompatibleFieldMappingItems: [ + { + fieldName: 'event.category', + expectedValue: 'keyword', + actualValue: 'constant_keyword', + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + }, + { + fieldName: 'host.name', + expectedValue: 'keyword', + actualValue: 'text', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + }, + { + fieldName: 'source.ip', + expectedValue: 'ip', + actualValue: 'text', + description: 'IP address of the source (IPv4 or IPv6).', + }, + ], + incompatibleFieldValueItems: [ + { + fieldName: 'event.category', + expectedValues: [ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ], + actualValues: [{ name: 'an_invalid_category', count: 2 }], + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + }, + ], + sameFamilyFieldCount: 1, + sameFamilyFields: ['agent.type'], + sameFamilyFieldItems: [ + { + fieldName: 'agent.type', + expectedValue: 'keyword', + actualValue: 'constant_keyword', + description: + 'Type of the agent.\nThe agent type always stays the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', + }, + ], + unallowedMappingFields: ['event.category', 'host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + sizeInBytes: 5000, + ilmPhase: 'hot', + markdownComments: ['test comments'], + ecsVersion: '1.0.0', + indexId: 'testIndexId', + error: null, + }; + + expect(formatStorageResult(inputData)).toEqual(expectedResult); + }); +}); + +describe('postStorageResult', () => { + const { fetch } = httpServiceMock.createStartContract(); + const { toasts } = notificationServiceMock.createStartContract(); + beforeEach(() => { + fetch.mockClear(); + }); + + test('it posts the result', async () => { + const storageResult = { indexName: 'test' } as unknown as StorageResult; + await postStorageResult({ + storageResult, + httpFetch: fetch, + abortController: new AbortController(), + toasts, + }); + + expect(fetch).toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify(storageResult), + }) + ); + }); + + test('it throws error', async () => { + const storageResult = { indexName: 'test' } as unknown as StorageResult; + fetch.mockRejectedValueOnce('test-error'); + await postStorageResult({ + httpFetch: fetch, + storageResult, + abortController: new AbortController(), + toasts, + }); + expect(toasts.addError).toHaveBeenCalledWith('test-error', { title: expect.any(String) }); + }); +}); + +describe('getStorageResults', () => { + const { fetch } = httpServiceMock.createStartContract(); + const { toasts } = notificationServiceMock.createStartContract(); + beforeEach(() => { + fetch.mockClear(); + }); + + test('it gets the results', async () => { + await getStorageResults({ + httpFetch: fetch, + abortController: new AbortController(), + pattern: 'auditbeat-*', + toasts, + }); + + expect(fetch).toHaveBeenCalledWith( + '/internal/ecs_data_quality_dashboard/results_latest/auditbeat-*', + expect.objectContaining({ + method: 'GET', + }) + ); + }); + + it('should catch error', async () => { + fetch.mockRejectedValueOnce('test-error'); + + const results = await getStorageResults({ + httpFetch: fetch, + abortController: new AbortController(), + pattern: 'auditbeat-*', + toasts, + }); + + expect(toasts.addError).toHaveBeenCalledWith('test-error', { title: expect.any(String) }); + expect(results).toEqual([]); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.ts new file mode 100644 index 0000000000000..b7b3b120441d3 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/hooks/use_results_rollup/utils/storage.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpHandler } from '@kbn/core-http-browser'; +import { IToasts } from '@kbn/core-notifications-browser'; + +import { + DataQualityCheckResult, + DataQualityIndexCheckedParams, + IncompatibleFieldMappingItem, + IncompatibleFieldValueItem, + PartitionedFieldMetadata, + SameFamilyFieldItem, +} from '../../../types'; +import { StorageResult } from '../types'; +import { GET_INDEX_RESULTS_LATEST, POST_INDEX_RESULTS } from '../constants'; +import { INTERNAL_API_VERSION } from '../../../constants'; +import { GET_RESULTS_ERROR_TITLE, POST_RESULT_ERROR_TITLE } from '../../../translations'; + +export const formatStorageResult = ({ + result, + report, + partitionedFieldMetadata, +}: { + result: DataQualityCheckResult; + report: DataQualityIndexCheckedParams; + partitionedFieldMetadata: PartitionedFieldMetadata; +}): StorageResult => { + const incompatibleFieldMappingItems: IncompatibleFieldMappingItem[] = []; + const incompatibleFieldValueItems: IncompatibleFieldValueItem[] = []; + const sameFamilyFieldItems: SameFamilyFieldItem[] = []; + + partitionedFieldMetadata.incompatible.forEach((field) => { + if (field.type !== field.indexFieldType) { + incompatibleFieldMappingItems.push({ + fieldName: field.indexFieldName, + expectedValue: field.type, + actualValue: field.indexFieldType, + description: field.description, + }); + } + + if (field.indexInvalidValues.length > 0) { + incompatibleFieldValueItems.push({ + fieldName: field.indexFieldName, + expectedValues: field.allowed_values?.map((x) => x.name) ?? [], + actualValues: field.indexInvalidValues.map((v) => ({ name: v.fieldName, count: v.count })), + description: field.description, + }); + } + }); + + partitionedFieldMetadata.sameFamily.forEach((field) => { + sameFamilyFieldItems.push({ + fieldName: field.indexFieldName, + expectedValue: field.type, + actualValue: field.indexFieldType, + description: field.description, + }); + }); + + return { + batchId: report.batchId, + indexName: result.indexName, + indexPattern: result.pattern, + isCheckAll: report.isCheckAll, + checkedAt: result.checkedAt ?? Date.now(), + docsCount: result.docsCount ?? 0, + totalFieldCount: partitionedFieldMetadata.all.length, + ecsFieldCount: partitionedFieldMetadata.ecsCompliant.length, + customFieldCount: partitionedFieldMetadata.custom.length, + incompatibleFieldCount: partitionedFieldMetadata.incompatible.length, + incompatibleFieldMappingItems, + incompatibleFieldValueItems, + sameFamilyFieldCount: partitionedFieldMetadata.sameFamily.length, + sameFamilyFields: report.sameFamilyFields ?? [], + sameFamilyFieldItems, + unallowedMappingFields: report.unallowedMappingFields ?? [], + unallowedValueFields: report.unallowedValueFields ?? [], + sizeInBytes: report.sizeInBytes ?? 0, + ilmPhase: result.ilmPhase, + markdownComments: result.markdownComments, + ecsVersion: report.ecsVersion, + indexId: report.indexId ?? '', + error: result.error, + }; +}; + +export const formatResultFromStorage = ({ + storageResult, + pattern, +}: { + storageResult: StorageResult; + pattern: string; +}): DataQualityCheckResult => ({ + docsCount: storageResult.docsCount, + error: storageResult.error, + ilmPhase: storageResult.ilmPhase, + incompatible: storageResult.incompatibleFieldCount, + indexName: storageResult.indexName, + markdownComments: storageResult.markdownComments, + sameFamily: storageResult.sameFamilyFieldCount, + checkedAt: storageResult.checkedAt, + pattern, +}); + +export async function postStorageResult({ + storageResult, + httpFetch, + toasts, + abortController = new AbortController(), +}: { + storageResult: StorageResult; + httpFetch: HttpHandler; + toasts: IToasts; + abortController?: AbortController; +}): Promise { + try { + await httpFetch(POST_INDEX_RESULTS, { + method: 'POST', + signal: abortController.signal, + version: INTERNAL_API_VERSION, + body: JSON.stringify(storageResult), + }); + } catch (err) { + toasts.addError(err, { title: POST_RESULT_ERROR_TITLE }); + } +} + +export async function getStorageResults({ + pattern, + httpFetch, + toasts, + abortController, +}: { + pattern: string; + httpFetch: HttpHandler; + toasts: IToasts; + abortController: AbortController; +}): Promise { + try { + const route = GET_INDEX_RESULTS_LATEST.replace('{pattern}', pattern); + const results = await httpFetch(route, { + method: 'GET', + signal: abortController.signal, + version: INTERNAL_API_VERSION, + }); + return results; + } catch (err) { + toasts.addError(err, { title: GET_RESULTS_ERROR_TITLE }); + return []; + } +} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.test.tsx new file mode 100644 index 0000000000000..b7ea6613dac62 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DARK_THEME } from '@elastic/charts'; +import { render, screen } from '@testing-library/react'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import React from 'react'; + +import { TestExternalProviders } from './mock/test_providers/test_providers'; +import { mockUseResultsRollup } from './mock/use_results_rollup/mock_use_results_rollup'; +import { getCheckState } from './stub/get_check_state'; +import * as useResultsRollup from './hooks/use_results_rollup'; +import * as useIndicesCheck from './hooks/use_indices_check'; +import { DataQualityPanel } from '.'; + +jest.mock('./data_quality_details/indices_details/pattern/hooks/use_stats', () => ({ + useStats: jest.fn(() => ({ + stats: {}, + error: null, + loading: false, + })), +})); + +jest.mock('./data_quality_details/indices_details/pattern/hooks/use_ilm_explain', () => ({ + useIlmExplain: jest.fn(() => ({ + error: null, + ilmExplain: {}, + loading: false, + })), +})); + +jest.spyOn(useResultsRollup, 'useResultsRollup').mockImplementation(() => mockUseResultsRollup); + +jest.spyOn(useIndicesCheck, 'useIndicesCheck').mockImplementation(() => ({ + checkIndex: jest.fn(), + checkState: { + ...getCheckState('auditbeat-*'), + }, +})); + +const { toasts } = notificationServiceMock.createSetupContract(); + +const patterns = ['auditbeat-*']; + +describe('DataQualityPanel', () => { + beforeEach(() => { + jest.clearAllMocks(); + + render( + + + + ); + }); + + it('renders the data quality summary', () => { + expect(screen.getByTestId('dataQualitySummary')).toBeInTheDocument(); + }); + + it(`renders the '${patterns.join(', ')}' patterns`, () => { + for (const pattern of patterns) { + expect(screen.getByTestId(`${pattern}PatternPanel`)).toBeInTheDocument(); + } + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.tsx similarity index 84% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.tsx index 1fcedc7f76a82..7d1a106d83570 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/index.tsx @@ -11,16 +11,16 @@ import type { PartialTheme, Theme } from '@elastic/charts'; import React, { useCallback, useMemo, useState } from 'react'; import type { IToasts } from '@kbn/core-notifications-browser'; -import { EuiComboBoxOptionOption } from '@elastic/eui'; -import { Body } from './data_quality_panel/body'; -import { DataQualityProvider } from './data_quality_panel/data_quality_context'; -import { EMPTY_STAT } from './helpers'; +import { EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { DataQualityProvider } from './data_quality_context'; import { ReportDataQualityCheckAllCompleted, ReportDataQualityIndexChecked } from './types'; import { ResultsRollupContext } from './contexts/results_rollup_context'; import { IndicesCheckContext } from './contexts/indices_check_context'; -import { useIndicesCheck } from './use_indices_check'; -import { useResultsRollup } from './use_results_rollup'; -import { ilmPhaseOptionsStatic } from './constants'; +import { useIndicesCheck } from './hooks/use_indices_check'; +import { useResultsRollup } from './hooks/use_results_rollup'; +import { ilmPhaseOptionsStatic, EMPTY_STAT } from './constants'; +import { DataQualitySummary } from './data_quality_summary'; +import { DataQualityDetails } from './data_quality_details'; interface Props { toasts: IToasts; @@ -141,7 +141,16 @@ const DataQualityPanelComponent: React.FC = ({ > - + + + + + + + + + + diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/allowed_values/mock_allowed_values.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/allowed_values/mock_allowed_values.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/allowed_values/mock_allowed_values.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/allowed_values/mock_allowed_values.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/data_quality_check_result/mock_index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/data_quality_check_result/mock_index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/enriched_field_metadata/mock_enriched_field_metadata.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/enriched_field_metadata/mock_enriched_field_metadata.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/ilm_explain/mock_ilm_explain.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/ilm_explain/mock_ilm_explain.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/ilm_explain/mock_ilm_explain.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/mappings_properties/mock_mappings_properties.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_properties/mock_mappings_properties.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/mappings_properties/mock_mappings_properties.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/mappings_response/mock_mappings_response.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/mappings_response/mock_mappings_response.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/mappings_response/mock_mappings_response.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/partitioned_field_metadata/mock_partitioned_field_metadata.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_alerts_pattern_rollup.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_alerts_pattern_rollup.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/stats/mock_stats.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/stats/mock_stats.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_auditbeat_index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/stats/mock_stats_auditbeat_index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_auditbeat_index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/stats/mock_stats_auditbeat_index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_packetbeat_index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/stats/mock_stats_packetbeat_index.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/stats/mock_stats_packetbeat_index.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/stats/mock_stats_packetbeat_index.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index 713b01e8ef71e..922d9b54612a6 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -16,14 +16,11 @@ import { ThemeProvider } from 'styled-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Theme } from '@elastic/charts'; -import { - DataQualityProvider, - DataQualityProviderProps, -} from '../../data_quality_panel/data_quality_context'; +import { DataQualityProvider, DataQualityProviderProps } from '../../data_quality_context'; import { ResultsRollupContext } from '../../contexts/results_rollup_context'; import { IndicesCheckContext } from '../../contexts/indices_check_context'; -import { UseIndicesCheckReturnValue } from '../../use_indices_check/types'; -import { UseResultsRollupReturnValue } from '../../use_results_rollup/types'; +import { UseIndicesCheckReturnValue } from '../../hooks/use_indices_check/types'; +import { UseResultsRollupReturnValue } from '../../hooks/use_results_rollup/types'; import { getMergeResultsRollupContextProps } from './utils/get_merged_results_rollup_context_props'; import { getMergedDataQualityContextProps } from './utils/get_merged_data_quality_context_props'; import { getMergedIndicesCheckContextProps } from './utils/get_merged_indices_check_context_props'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_data_quality_context_props.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_data_quality_context_props.ts similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_data_quality_context_props.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_data_quality_context_props.ts index d4cac9198e9c8..264198e510b5e 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_data_quality_context_props.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_data_quality_context_props.ts @@ -7,8 +7,8 @@ import numeral from '@elastic/numeral'; -import { DataQualityProviderProps } from '../../../data_quality_panel/data_quality_context'; -import { EMPTY_STAT } from '../../../helpers'; +import { DataQualityProviderProps } from '../../../data_quality_context'; +import { EMPTY_STAT } from '../../../constants'; export const getMergedDataQualityContextProps = ( dataQualityContextProps?: Partial diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_indices_check_context_props.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_indices_check_context_props.ts similarity index 95% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_indices_check_context_props.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_indices_check_context_props.ts index 28129d7e8155f..74817057ee377 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_indices_check_context_props.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_indices_check_context_props.ts @@ -9,7 +9,7 @@ import { getCheckState } from '../../../stub/get_check_state'; import { UseIndicesCheckCheckState, UseIndicesCheckReturnValue, -} from '../../../use_indices_check/types'; +} from '../../../hooks/use_indices_check/types'; export const getMergedIndicesCheckContextProps = ( patternIndexNames: Record, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_results_rollup_context_props.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_results_rollup_context_props.ts similarity index 57% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_results_rollup_context_props.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_results_rollup_context_props.ts index bb8e3e6967b4d..352b50ec1ef2a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/test_providers/utils/get_merged_results_rollup_context_props.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/test_providers/utils/get_merged_results_rollup_context_props.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { UseResultsRollupReturnValue } from '../../../use_results_rollup/types'; -import { auditbeatWithAllResults } from '../../pattern_rollup/mock_auditbeat_pattern_rollup'; +import { UseResultsRollupReturnValue } from '../../../hooks/use_results_rollup/types'; +import { mockUseResultsRollup } from '../../use_results_rollup/mock_use_results_rollup'; export const getMergeResultsRollupContextProps = ( resultsRollupContextProps?: Partial @@ -24,25 +24,7 @@ export const getMergeResultsRollupContextProps = ( updatePatternIndexNames, updatePatternRollup, } = { - onCheckCompleted: jest.fn(), - patternIndexNames: { - 'auditbeat-*': [ - '.ds-auditbeat-8.6.1-2023.02.07-000001', - 'auditbeat-custom-index-1', - 'auditbeat-custom-empty-index-1', - ], - }, - patternRollups: { - 'auditbeat-*': auditbeatWithAllResults, - }, - totalDocsCount: 19127, - totalIncompatible: 4, - totalIndices: 3, - totalIndicesChecked: 3, - totalSameFamily: 0, - totalSizeInBytes: 18820446, - updatePatternIndexNames: jest.fn(), - updatePatternRollup: jest.fn(), + ...mockUseResultsRollup, ...resultsRollupContextProps, }; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/unallowed_values/mock_unallowed_values.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/unallowed_values/mock_unallowed_values.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/unallowed_values/mock_unallowed_values.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/use_results_rollup/mock_use_results_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/use_results_rollup/mock_use_results_rollup.ts new file mode 100644 index 0000000000000..d3259d8c4c6d8 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/mock/use_results_rollup/mock_use_results_rollup.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { auditbeatWithAllResults } from '../pattern_rollup/mock_auditbeat_pattern_rollup'; + +export const mockUseResultsRollup = { + onCheckCompleted: jest.fn(), + patternIndexNames: { + 'auditbeat-*': [ + '.ds-auditbeat-8.6.1-2023.02.07-000001', + 'auditbeat-custom-index-1', + 'auditbeat-custom-empty-index-1', + ], + }, + patternRollups: { + 'auditbeat-*': auditbeatWithAllResults, + }, + totalDocsCount: 19127, + totalIncompatible: 4, + totalIndices: 3, + totalIndicesChecked: 3, + totalSameFamily: 0, + totalSizeInBytes: 18820446, + updatePatternIndexNames: jest.fn(), + updatePatternRollup: jest.fn(), +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/stat/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stat/index.test.tsx similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/stat/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stat/index.test.tsx index 6979be1c8af32..0db273d5d3024 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/stat/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stat/index.test.tsx @@ -10,7 +10,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Props, Stat, arePropsEqualOneLevelDeep } from '.'; -import { TestExternalProviders } from '../../../../../mock/test_providers/test_providers'; +import { TestExternalProviders } from '../mock/test_providers/test_providers'; describe('Stat', () => { it('renders stat with badge', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/stat/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stat/index.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/stat/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stat/index.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stat_label/translations.ts similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/stat_label/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stat_label/translations.ts diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stats_rollup/index.test.tsx similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.test.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stats_rollup/index.test.tsx index ba470748727bc..6b8106f33c157 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stats_rollup/index.test.tsx @@ -12,7 +12,7 @@ import userEvent from '@testing-library/user-event'; import { TestDataQualityProviders, TestExternalProviders, -} from '../../../../mock/test_providers/test_providers'; +} from '../mock/test_providers/test_providers'; import { StatsRollup } from '.'; describe('StatsRollup', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stats_rollup/index.tsx similarity index 92% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stats_rollup/index.tsx index 0a36dbf0e3ab3..7a0fb2deaf5bb 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/stats_rollup/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stats_rollup/index.tsx @@ -9,10 +9,11 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; -import { EMPTY_STAT, getIncompatibleStatBadgeColor } from '../../../../helpers'; -import { useDataQualityContext } from '../../../data_quality_context'; -import * as i18n from '../../../stat_label/translations'; -import { Stat } from './stat'; +import { EMPTY_STAT } from '../constants'; +import { useDataQualityContext } from '../data_quality_context'; +import * as i18n from '../stat_label/translations'; +import { Stat } from '../stat'; +import { getIncompatibleStatBadgeColor } from '../utils/get_incompatible_stat_badge_color'; const StyledStatWrapperFlexItem = styled(EuiFlexItem)` padding: 0 ${({ theme }) => theme.eui.euiSize}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/stub/get_check_state/index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stub/get_check_state/index.ts similarity index 84% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/stub/get_check_state/index.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stub/get_check_state/index.ts index c1ba27ac91376..12f45c192aa25 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/stub/get_check_state/index.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/stub/get_check_state/index.ts @@ -10,11 +10,11 @@ import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/ import { getMappingsProperties, getSortedPartitionedFieldMetadata, -} from '../../data_quality_panel/index_properties/helpers'; +} from '../../data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers'; import { mockMappingsResponse } from '../../mock/mappings_response/mock_mappings_response'; -import { UseIndicesCheckCheckState } from '../../use_indices_check/types'; -import { getUnallowedValues } from '../../use_unallowed_values/helpers'; -import { getUnallowedValueRequestItems } from '../../data_quality_panel/allowed_values/helpers'; +import { UseIndicesCheckCheckState } from '../../hooks/use_indices_check/types'; +import { getUnallowedValues } from '../../utils/fetch_unallowed_values'; +import { getUnallowedValueRequestItems } from '../../utils/get_unallowed_value_request_items'; import { EcsFlatTyped } from '../../constants'; import { mockUnallowedValuesResponse } from '../../mock/unallowed_values/mock_unallowed_values'; import { UnallowedValueSearchResult } from '../../types'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/styles.tsx similarity index 100% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/styles.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/styles.tsx diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/translations.ts similarity index 79% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/translations.ts index 6c78513274de8..3497aa89d97ee 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/translations.ts @@ -42,13 +42,6 @@ export const COLD_DESCRIPTION = i18n.translate( } ); -export const COLD_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => - i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.coldPatternTooltip', { - values: { indices, pattern }, - defaultMessage: - '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} cold. Cold indices are no longer being updated and are queried infrequently. The information still needs to be searchable, but it’s okay if those queries are slower.', - }); - export const COPIED_RESULTS_TOAST_TITLE = i18n.translate( 'securitySolutionPackages.ecsDataQualityDashboard.toasts.copiedResultsToastTitle', { @@ -166,18 +159,6 @@ export const FROZEN_DESCRIPTION = i18n.translate( } ); -export const FROZEN_PATTERN_TOOLTIP = ({ - indices, - pattern, -}: { - indices: number; - pattern: string; -}) => - i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.frozenPatternTooltip', { - values: { indices, pattern }, - defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} frozen. Frozen indices are no longer being updated and are queried rarely. The information still needs to be searchable, but it's okay if those queries are extremely slow.`, - }); - export const HOT_DESCRIPTION = i18n.translate( 'securitySolutionPackages.ecsDataQualityDashboard.hotDescription', { @@ -185,13 +166,6 @@ export const HOT_DESCRIPTION = i18n.translate( } ); -export const HOT_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => - i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.hotPatternTooltip', { - values: { indices, pattern }, - defaultMessage: - '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} hot. Hot indices are actively being updated and queried.', - }); - /** The tooltip for the `ILM phase` combo box on the Data Quality Dashboard */ export const INDEX_LIFECYCLE_MANAGEMENT_PHASES: string = i18n.translate( 'securitySolutionPackages.ecsDataQualityDashboard.indexLifecycleManagementPhasesTooltip', @@ -267,18 +241,6 @@ export const UNMANAGED_DESCRIPTION = i18n.translate( } ); -export const UNMANAGED_PATTERN_TOOLTIP = ({ - indices, - pattern, -}: { - indices: number; - pattern: string; -}) => - i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.unmanagedPatternTooltip', { - values: { indices, pattern }, - defaultMessage: `{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} unmanaged by Index Lifecycle Management (ILM)`, - }); - export const WARM_DESCRIPTION = i18n.translate( 'securitySolutionPackages.ecsDataQualityDashboard.warmDescription', { @@ -286,13 +248,6 @@ export const WARM_DESCRIPTION = i18n.translate( } ); -export const WARM_PATTERN_TOOLTIP = ({ indices, pattern }: { indices: number; pattern: string }) => - i18n.translate('securitySolutionPackages.ecsDataQualityDashboard.warmPatternTooltip', { - values: { indices, pattern }, - defaultMessage: - '{indices} {indices, plural, =1 {index} other {indices}} matching the {pattern} pattern {indices, plural, =1 {is} other {are}} warm. Warm indices are no longer being updated but are still being queried.', - }); - export const POST_RESULT_ERROR_TITLE = i18n.translate( 'securitySolutionPackages.ecsDataQualityDashboard.postResultErrorTitle', { defaultMessage: 'Error writing saved data quality check results' } diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/types.ts similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/types.ts index dd5dd6dd0a159..916d0f377bf85 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/types.ts @@ -85,14 +85,6 @@ export interface PartitionedFieldMetadata { sameFamily: EcsBasedFieldMetadata[]; } -export interface PartitionedFieldMetadataStats { - all: number; - custom: number; - ecsCompliant: number; - incompatible: number; - sameFamily: number; -} - export interface UnallowedValueRequestItem { allowedValues: string[]; indexFieldName: string; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/check_index.test.ts similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/check_index.test.ts index 9ea197360356f..2b90f67966c77 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/check_index.test.ts @@ -6,37 +6,40 @@ */ import { checkIndex, EMPTY_PARTITIONED_FIELD_METADATA } from './check_index'; -import { EMPTY_STAT } from '../helpers'; import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_response'; import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values'; import { UnallowedValueRequestItem, UnallowedValueSearchResult } from '../types'; import { getMappingsProperties, getSortedPartitionedFieldMetadata, -} from '../data_quality_panel/index_properties/helpers'; +} from '../data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers'; import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; -import { getUnallowedValues } from '../use_unallowed_values/helpers'; -import { getUnallowedValueRequestItems } from '../data_quality_panel/allowed_values/helpers'; -import { EcsFlatTyped } from '../constants'; +import { getUnallowedValues } from './fetch_unallowed_values'; +import { getUnallowedValueRequestItems } from './get_unallowed_value_request_items'; +import { EcsFlatTyped, EMPTY_STAT } from '../constants'; let mockFetchMappings = jest.fn( (_: { abortController: AbortController; patternOrIndexName: string }) => Promise.resolve(mockMappingsResponse) ); -jest.mock('../use_mappings/helpers', () => ({ - fetchMappings: ({ - abortController, - patternOrIndexName, - }: { - abortController: AbortController; - patternOrIndexName: string; - }) => - mockFetchMappings({ +jest.mock('./fetch_mappings', () => { + const original = jest.requireActual('./fetch_mappings'); + return { + ...original, + fetchMappings: ({ abortController, patternOrIndexName, - }), -})); + }: { + abortController: AbortController; + patternOrIndexName: string; + }) => + mockFetchMappings({ + abortController, + patternOrIndexName, + }), + }; +}); const mockFetchUnallowedValues = jest.fn( (_: { @@ -46,8 +49,8 @@ const mockFetchUnallowedValues = jest.fn( }) => Promise.resolve(mockUnallowedValuesResponse) ); -jest.mock('../use_unallowed_values/helpers', () => { - const original = jest.requireActual('../use_unallowed_values/helpers'); +jest.mock('./fetch_unallowed_values', () => { + const original = jest.requireActual('./fetch_unallowed_values'); return { ...original, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/check_index.ts similarity index 91% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/check_index.ts index 8dd282c4121f0..76094c97f5667 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/utils/check_index.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/check_index.ts @@ -12,20 +12,20 @@ import { } from '@elastic/elasticsearch/lib/api/types'; import { v4 as uuidv4 } from 'uuid'; -import { getUnallowedValueRequestItems } from '../data_quality_panel/allowed_values/helpers'; +import { getUnallowedValueRequestItems } from './get_unallowed_value_request_items'; import { getMappingsProperties, getSortedPartitionedFieldMetadata, -} from '../data_quality_panel/index_properties/helpers'; -import * as i18n from '../data_quality_panel/data_quality_summary/summary_actions/check_all/translations'; +} from '../data_quality_details/indices_details/pattern/index_check_flyout/index_properties/helpers'; +import * as i18n from '../data_quality_summary/summary_actions/check_all/translations'; import type { OnCheckCompleted, PartitionedFieldMetadata, UnallowedValueCount, UnallowedValueSearchResult, } from '../types'; -import { fetchMappings } from '../use_mappings/helpers'; -import { fetchUnallowedValues, getUnallowedValues } from '../use_unallowed_values/helpers'; +import { fetchMappings } from './fetch_mappings'; +import { fetchUnallowedValues, getUnallowedValues } from './fetch_unallowed_values'; import { EcsFlatTyped } from '../constants'; export const EMPTY_PARTITIONED_FIELD_METADATA: PartitionedFieldMetadata = { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_mappings.test.ts similarity index 97% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_mappings.test.ts index b3a31228bb059..b24241b95a510 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_mappings.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { fetchMappings } from './helpers'; +import { fetchMappings } from './fetch_mappings'; import { mockMappingsResponse } from '../mock/mappings_response/mock_mappings_response'; describe('helpers', () => { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_mappings.ts similarity index 96% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_mappings.ts index 6bee012883c05..090f7e375e5bc 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_mappings/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_mappings.ts @@ -9,7 +9,7 @@ import type { HttpHandler } from '@kbn/core-http-browser'; import type { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; import * as i18n from '../translations'; -import { INTERNAL_API_VERSION } from '../helpers'; +import { INTERNAL_API_VERSION } from '../constants'; export const MAPPINGS_API_ROUTE = '/internal/ecs_data_quality_dashboard/mappings'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_unallowed_values.test.ts similarity index 99% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_unallowed_values.test.ts index ade9970277c50..00dc04dedf27a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_unallowed_values.test.ts @@ -12,10 +12,10 @@ import { getUnallowedValueCount, getUnallowedValues, isBucket, -} from './helpers'; +} from './fetch_unallowed_values'; import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values'; import { UnallowedValueRequestItem, UnallowedValueSearchResult } from '../types'; -import { INTERNAL_API_VERSION } from '../helpers'; +import { INTERNAL_API_VERSION } from '../constants'; describe('helpers', () => { let originalFetch: (typeof global)['fetch']; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_unallowed_values.ts similarity index 98% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_unallowed_values.ts index aa25a5fe00b44..b19481546a285 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/fetch_unallowed_values.ts @@ -6,7 +6,8 @@ */ import type { HttpHandler } from '@kbn/core-http-browser'; -import { INTERNAL_API_VERSION } from '../helpers'; + +import { INTERNAL_API_VERSION } from '../constants'; import * as i18n from '../translations'; import type { Bucket, @@ -15,8 +16,6 @@ import type { UnallowedValueSearchResult, } from '../types'; -const UNALLOWED_VALUES_API_ROUTE = '/internal/ecs_data_quality_dashboard/unallowed_field_values'; - export const isBucket = (maybeBucket: unknown): maybeBucket is Bucket => maybeBucket != null && typeof (maybeBucket as Bucket).key === 'string' && @@ -65,6 +64,7 @@ export const getUnallowedValues = ({ }, {}); }; +const UNALLOWED_VALUES_API_ROUTE = '/internal/ecs_data_quality_dashboard/unallowed_field_values'; export async function fetchUnallowedValues({ abortController, httpFetch, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_ilm_phase_description.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_ilm_phase_description.test.ts new file mode 100644 index 0000000000000..b2ff66941b395 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_ilm_phase_description.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getIlmPhaseDescription } from './get_ilm_phase_description'; +import { + COLD_DESCRIPTION, + FROZEN_DESCRIPTION, + HOT_DESCRIPTION, + UNMANAGED_DESCRIPTION, + WARM_DESCRIPTION, +} from '../translations'; + +describe('helpers', () => { + describe('getIlmPhaseDescription', () => { + const phases: Array<{ + phase: string; + expected: string; + }> = [ + { + phase: 'hot', + expected: HOT_DESCRIPTION, + }, + { + phase: 'warm', + expected: WARM_DESCRIPTION, + }, + { + phase: 'cold', + expected: COLD_DESCRIPTION, + }, + { + phase: 'frozen', + expected: FROZEN_DESCRIPTION, + }, + { + phase: 'unmanaged', + expected: UNMANAGED_DESCRIPTION, + }, + { + phase: 'something-else', + expected: ' ', + }, + ]; + + phases.forEach(({ phase, expected }) => { + test(`it returns ${expected} when phase is ${phase}`, () => { + expect(getIlmPhaseDescription(phase)).toBe(expected); + }); + }); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_ilm_phase_description.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_ilm_phase_description.ts new file mode 100644 index 0000000000000..27f20cd28e1a4 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_ilm_phase_description.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as i18n from '../translations'; + +/** + * Returns an i18n description of an an ILM phase + */ +export const getIlmPhaseDescription = (phase: string): string => { + switch (phase) { + case 'hot': + return i18n.HOT_DESCRIPTION; + case 'warm': + return i18n.WARM_DESCRIPTION; + case 'cold': + return i18n.COLD_DESCRIPTION; + case 'frozen': + return i18n.FROZEN_DESCRIPTION; + case 'unmanaged': + return i18n.UNMANAGED_DESCRIPTION; + default: + return ' '; + } +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_incompatible_stat_badge_color.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_incompatible_stat_badge_color.test.ts new file mode 100644 index 0000000000000..44283436f7477 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_incompatible_stat_badge_color.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getIncompatibleStatBadgeColor } from './get_incompatible_stat_badge_color'; + +describe('getIncompatibleStatBadgeColor', () => { + describe('when incompatible is greater than 0', () => { + it('returns danger', () => { + expect(getIncompatibleStatBadgeColor(1)).toBe('danger'); + }); + }); + + describe('when incompatible is 0', () => { + it('returns hollow', () => { + expect(getIncompatibleStatBadgeColor(0)).toBe('hollow'); + }); + }); + + describe('when incompatible is undefined', () => { + it('returns hollow', () => { + expect(getIncompatibleStatBadgeColor(undefined)).toBe('hollow'); + }); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_incompatible_stat_badge_color.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_incompatible_stat_badge_color.ts new file mode 100644 index 0000000000000..fcaa660bba934 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_incompatible_stat_badge_color.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getIncompatibleStatBadgeColor = (incompatible: number | undefined): string => + incompatible != null && incompatible > 0 ? 'danger' : 'hollow'; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_unallowed_value_request_items.tsx similarity index 94% rename from x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx rename to x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_unallowed_value_request_items.tsx index fd356b9fe60d5..8ce15c11b7000 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_unallowed_value_request_items.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { EcsFlatTyped } from '../../constants'; -import type { EcsFieldMetadata, UnallowedValueRequestItem } from '../../types'; +import type { EcsFlatTyped } from '../constants'; +import type { EcsFieldMetadata, UnallowedValueRequestItem } from '../types'; export const hasAllowedValues = ({ ecsMetadata, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_unallowed_values_request_items.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_unallowed_values_request_items.test.tsx new file mode 100644 index 0000000000000..c2a35bc989625 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/get_unallowed_values_request_items.test.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EcsFlatTyped } from '../constants'; +import { + getUnallowedValueRequestItems, + getValidValues, + hasAllowedValues, +} from './get_unallowed_value_request_items'; + +describe('hasAllowedValues', () => { + test('it returns true for a field that has `allowed_values`', () => { + expect( + hasAllowedValues({ + ecsMetadata: EcsFlatTyped, + fieldName: 'event.category', + }) + ).toBe(true); + }); + + test('it returns false for a field that does NOT have `allowed_values`', () => { + expect( + hasAllowedValues({ + ecsMetadata: EcsFlatTyped, + fieldName: 'host.name', + }) + ).toBe(false); + }); + + test('it returns false for a field that does NOT exist in `ecsMetadata`', () => { + expect( + hasAllowedValues({ + ecsMetadata: EcsFlatTyped, + fieldName: 'does.NOT.exist', + }) + ).toBe(false); + }); +}); + +describe('getValidValues', () => { + test('it returns the expected valid values', () => { + expect(getValidValues(EcsFlatTyped['event.category'])).toEqual( + expect.arrayContaining([ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ]) + ); + }); + + test('it returns an empty array when the `field` does NOT have `allowed_values`', () => { + expect(getValidValues(EcsFlatTyped['host.name'])).toEqual([]); + }); + + test('it returns an empty array when `field` is undefined', () => { + expect(getValidValues(undefined)).toEqual([]); + }); +}); + +describe('getUnallowedValueRequestItems', () => { + test('it returns the expected request items', () => { + expect( + getUnallowedValueRequestItems({ + ecsMetadata: EcsFlatTyped, + indexName: 'auditbeat-*', + }) + ).toEqual([ + { + indexName: 'auditbeat-*', + indexFieldName: 'event.category', + allowedValues: expect.arrayContaining([ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ]), + }, + { + indexName: 'auditbeat-*', + indexFieldName: 'event.kind', + allowedValues: expect.arrayContaining([ + 'alert', + 'enrichment', + 'event', + 'metric', + 'state', + 'pipeline_error', + 'signal', + ]), + }, + { + indexName: 'auditbeat-*', + indexFieldName: 'event.outcome', + allowedValues: expect.arrayContaining(['failure', 'success', 'unknown']), + }, + { + indexName: 'auditbeat-*', + indexFieldName: 'event.type', + allowedValues: expect.arrayContaining([ + 'access', + 'admin', + 'allowed', + 'change', + 'connection', + 'creation', + 'deletion', + 'denied', + 'end', + 'error', + 'group', + 'indicator', + 'info', + 'installation', + 'protocol', + 'start', + 'user', + ]), + }, + ]); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/stats.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/stats.test.ts new file mode 100644 index 0000000000000..ad91466634ea7 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/stats.test.ts @@ -0,0 +1,252 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + auditbeatNoResults, + auditbeatWithAllResults, +} from '../mock/pattern_rollup/mock_auditbeat_pattern_rollup'; +import { packetbeatWithSomeErrors } from '../mock/pattern_rollup/mock_packetbeat_pattern_rollup'; +import { mockStatsPacketbeatIndex } from '../mock/stats/mock_stats_auditbeat_index'; +import { mockStatsAuditbeatIndex } from '../mock/stats/mock_stats_packetbeat_index'; +import { DataQualityCheckResult } from '../types'; +import { + getDocsCount, + getSizeInBytes, + getTotalPatternIncompatible, + getTotalPatternIndicesChecked, +} from './stats'; + +describe('getTotalPatternIndicesChecked', () => { + test('it returns zero when `patternRollup` is undefined', () => { + expect(getTotalPatternIndicesChecked(undefined)).toEqual(0); + }); + + test('it returns zero when `patternRollup` does NOT have any results', () => { + expect(getTotalPatternIndicesChecked(auditbeatNoResults)).toEqual(0); + }); + + test('it returns the expected total when all indices in `patternRollup` have results', () => { + expect(getTotalPatternIndicesChecked(auditbeatWithAllResults)).toEqual(3); + }); + + test('it returns the expected total when some indices in `patternRollup` have errors', () => { + expect(getTotalPatternIndicesChecked(packetbeatWithSomeErrors)).toEqual(1); + }); +}); + +describe('getDocsCount', () => { + test('it returns the expected docs count when `stats` contains the `indexName`', () => { + const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; + const expectedCount = mockStatsPacketbeatIndex[indexName].num_docs; + + expect( + getDocsCount({ + indexName, + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(expectedCount); + }); + + test('it returns zero when `stats` does NOT contain the `indexName`', () => { + const indexName = 'not-gonna-find-it'; + + expect( + getDocsCount({ + indexName, + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(0); + }); + + test('it returns zero when `stats` is null', () => { + const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; + + expect( + getDocsCount({ + indexName, + stats: null, + }) + ).toEqual(0); + }); + + test('it returns the expected total for a green index, where `primaries.docs.count` and `total.docs.count` have different values', () => { + const indexName = 'auditbeat-custom-index-1'; + + expect( + getDocsCount({ + indexName, + stats: mockStatsAuditbeatIndex, + }) + ).toEqual(mockStatsAuditbeatIndex[indexName].num_docs); + }); +}); + +describe('getSizeInBytes', () => { + test('it returns the expected size when `stats` contains the `indexName`', () => { + const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; + const expectedCount = mockStatsPacketbeatIndex[indexName].size_in_bytes; + + expect( + getSizeInBytes({ + indexName, + stats: mockStatsPacketbeatIndex, + }) + ).toEqual(expectedCount); + }); + + test('it returns undefined when `stats` does NOT contain the `indexName`', () => { + const indexName = 'not-gonna-find-it'; + + expect( + getSizeInBytes({ + indexName, + stats: mockStatsPacketbeatIndex, + }) + ).toBeUndefined(); + }); + + test('it returns undefined when `stats` is null', () => { + const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; + + expect( + getSizeInBytes({ + indexName, + stats: null, + }) + ).toBeUndefined(); + }); + + test('it returns the expected size for a green index, where `primaries.store.size_in_bytes` and `total.store.size_in_bytes` have different values', () => { + const indexName = 'auditbeat-custom-index-1'; + + expect( + getSizeInBytes({ + indexName, + stats: mockStatsAuditbeatIndex, + }) + ).toEqual(mockStatsAuditbeatIndex[indexName].size_in_bytes); + }); +}); + +describe('getTotalPatternIncompatible', () => { + test('it returns zero when multiple indices in the results results have a count of zero', () => { + const results: Record = { + '.ds-packetbeat-8.5.3-2023.02.04-000001': { + docsCount: 1630289, + error: null, + ilmPhase: 'hot', + incompatible: 0, + indexName: '.ds-packetbeat-8.5.3-2023.02.04-000001', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'packetbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + '.ds-packetbeat-8.6.1-2023.02.04-000001': { + docsCount: 1628343, + error: null, + ilmPhase: 'hot', + incompatible: 0, + indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'packetbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + }; + + expect(getTotalPatternIncompatible(results)).toEqual(0); + }); + + test("it returns the expected total when some indices have incompatible fields, but others don't", () => { + const results: Record = { + '.ds-auditbeat-8.6.1-2023.02.07-000001': { + docsCount: 18086, + error: null, + ilmPhase: 'hot', + incompatible: 0, + indexName: '.ds-auditbeat-8.6.1-2023.02.07-000001', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'auditbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + 'auditbeat-custom-index-1': { + docsCount: 4, + error: null, + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'auditbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + 'auditbeat-custom-empty-index-1': { + docsCount: 0, + error: null, + ilmPhase: 'unmanaged', + incompatible: 1, + indexName: 'auditbeat-custom-empty-index-1', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'auditbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + }; + + expect(getTotalPatternIncompatible(results)).toEqual(4); + }); + + test('it returns the expected total when some indices have undefined incompatible counts', () => { + const results: Record = { + '.ds-auditbeat-8.6.1-2023.02.07-000001': { + docsCount: 18086, + error: null, + ilmPhase: 'hot', + incompatible: undefined, // <-- this index has an undefined `incompatible` + indexName: '.ds-auditbeat-8.6.1-2023.02.07-000001', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'auditbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + 'auditbeat-custom-index-1': { + docsCount: 4, + error: null, + ilmPhase: 'unmanaged', + incompatible: 3, + indexName: 'auditbeat-custom-index-1', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'auditbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + 'auditbeat-custom-empty-index-1': { + docsCount: 0, + error: null, + ilmPhase: 'unmanaged', + incompatible: 1, + indexName: 'auditbeat-custom-empty-index-1', + markdownComments: ['foo', 'bar', 'baz'], + pattern: 'auditbeat-*', + sameFamily: 0, + checkedAt: Date.now(), + }, + }; + + expect(getTotalPatternIncompatible(results)).toEqual(4); + }); + + test('it returns zero when `results` is empty', () => { + expect(getTotalPatternIncompatible({})).toEqual(0); + }); + + test('it returns undefined when `results` is undefined', () => { + expect(getTotalPatternIncompatible(undefined)).toBeUndefined(); + }); +}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/stats.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/stats.ts new file mode 100644 index 0000000000000..b8f60be24a87c --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality_panel/utils/stats.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataQualityCheckResult, MeteringStatsIndex, PatternRollup } from '../types'; + +export const getSizeInBytes = ({ + indexName, + stats, +}: { + indexName: string; + stats: Record | null; +}): number | undefined => (stats && stats[indexName]?.size_in_bytes) ?? undefined; + +export const getDocsCount = ({ + indexName, + stats, +}: { + indexName: string; + stats: Record | null; +}): number => (stats && stats[indexName]?.num_docs) ?? 0; + +export const getTotalPatternIndicesChecked = (patternRollup: PatternRollup | undefined): number => { + if (patternRollup != null && patternRollup.results != null) { + const allResults = Object.values(patternRollup.results); + const nonErrorResults = allResults.filter(({ error }) => error == null); + + return nonErrorResults.length; + } else { + return 0; + } +}; + +export const getTotalPatternIncompatible = ( + results: Record | undefined +): number | undefined => { + if (results == null) { + return undefined; + } + + const allResults = Object.values(results); + + return allResults.reduce((acc, { incompatible }) => acc + (incompatible ?? 0), 0); +}; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts index 5f9ab020ea21f..96288f2bfec6c 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -export { DataQualityPanel } from './impl/data_quality'; +export { DataQualityPanel } from './impl/data_quality_panel'; -export { getIlmPhaseDescription } from './impl/data_quality/helpers'; +export { getIlmPhaseDescription } from './impl/data_quality_panel/utils/get_ilm_phase_description'; export { DATA_QUALITY_PROMPT_CONTEXT_PILL, @@ -18,6 +18,6 @@ export { INDEX_LIFECYCLE_MANAGEMENT_PHASES, SELECT_ONE_OR_MORE_ILM_PHASES, DATA_QUALITY_DASHBOARD_CONVERSATION_ID, -} from './impl/data_quality/translations'; +} from './impl/data_quality_panel/translations'; -export { ECS_REFERENCE_URL } from './impl/data_quality/data_quality_panel/index_properties/markdown/helpers'; +export { ECS_REFERENCE_URL } from './impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/index_properties/markdown/helpers'; diff --git a/x-pack/plugins/actions/server/lib/connector_token_client.test.ts b/x-pack/plugins/actions/server/lib/connector_token_client.test.ts index baedd2ff07beb..b2b8c7487b475 100644 --- a/x-pack/plugins/actions/server/lib/connector_token_client.test.ts +++ b/x-pack/plugins/actions/server/lib/connector_token_client.test.ts @@ -37,6 +37,7 @@ beforeAll(() => { beforeEach(() => { clock.reset(); jest.resetAllMocks(); + jest.restoreAllMocks(); connectorTokenClient = new ConnectorTokenClient({ unsecuredSavedObjectsClient, encryptedSavedObjectsClient, diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index 08d183771b53c..ccc1bb9ea8427 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -12,7 +12,7 @@ import { IndicesDataStreamIndex, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import { ReplaySubject, Subject } from 'rxjs'; +import { ReplaySubject, Subject, of } from 'rxjs'; import { AlertsService } from './alerts_service'; import { IRuleTypeAlerts, RecoveredActionGroup } from '../types'; import { retryUntil } from './test_utils'; @@ -219,6 +219,7 @@ const ruleTypeWithAlertDefinition: jest.Mocked = { describe('Alerts Service', () => { let pluginStop$: Subject; + const elasticsearchAndSOAvailability$ = of(true); beforeEach(() => { jest.resetAllMocks(); @@ -251,6 +252,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -275,6 +277,46 @@ describe('Alerts Service', () => { expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); }); + test('should not initialize common resources if ES is not ready', async () => { + const test$ = new Subject(); + const alertsService = new AlertsService({ + logger, + elasticsearchClientPromise: Promise.resolve(clusterClient), + pluginStop$, + kibanaVersion: '8.8.0', + dataStreamAdapter, + elasticsearchAndSOAvailability$: test$, + }); + + await retryUntil( + 'alert service initialized', + async () => alertsService.isInitialized() === true + ); + expect(alertsService.isInitialized()).toEqual(false); + + // ES is ready, should initialize the resources + test$.next(true); + await retryUntil( + 'alert service initialized', + async () => alertsService.isInitialized() === true + ); + expect(alertsService.isInitialized()).toEqual(true); + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledTimes( + useDataStreamForAlerts ? 0 : 1 + ); + if (!useDataStreamForAlerts) { + expect(clusterClient.ilm.putLifecycle).toHaveBeenCalledWith(IlmPutBody); + } + expect(clusterClient.cluster.putComponentTemplate).toHaveBeenCalledTimes(3); + + const componentTemplate1 = clusterClient.cluster.putComponentTemplate.mock.calls[0][0]; + expect(componentTemplate1.name).toEqual('.alerts-framework-mappings'); + const componentTemplate2 = clusterClient.cluster.putComponentTemplate.mock.calls[1][0]; + expect(componentTemplate2.name).toEqual('.alerts-legacy-alert-mappings'); + const componentTemplate3 = clusterClient.cluster.putComponentTemplate.mock.calls[2][0]; + expect(componentTemplate3.name).toEqual('.alerts-ecs-mappings'); + }); + test('should log error and set initialized to false if adding ILM policy throws error', async () => { if (useDataStreamForAlerts) return; @@ -285,6 +327,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil('error log called', async () => logger.error.mock.calls.length > 0); @@ -306,6 +349,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil('error log called', async () => logger.error.mock.calls.length > 0); @@ -386,6 +430,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -427,6 +472,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -1447,6 +1493,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -1508,6 +1555,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -1546,6 +1594,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -1644,6 +1693,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -1765,6 +1815,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -1846,6 +1897,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -1944,6 +1996,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -2006,6 +2059,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -2072,6 +2126,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -2145,6 +2200,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); alertsService.register(TestRegistrationContext); @@ -2198,6 +2254,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -2218,6 +2275,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -2238,6 +2296,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -2266,6 +2325,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -2297,6 +2357,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -2333,6 +2394,7 @@ describe('Alerts Service', () => { pluginStop$, kibanaVersion: '8.8.0', dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil( @@ -2367,6 +2429,7 @@ describe('Alerts Service', () => { kibanaVersion: '8.8.0', timeoutMs: 10, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil('error logger called', async () => logger.error.mock.calls.length > 0); @@ -2383,6 +2446,7 @@ describe('Alerts Service', () => { kibanaVersion: '8.8.0', timeoutMs: 10, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await retryUntil('debug logger called', async () => logger.debug.mock.calls.length > 0); diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts index 10161b4e09635..f37481c9ccb86 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.ts @@ -7,7 +7,7 @@ import { isEmpty, isEqual, omit } from 'lodash'; import { Logger, ElasticsearchClient } from '@kbn/core/server'; -import { Observable } from 'rxjs'; +import { filter, firstValueFrom, Observable } from 'rxjs'; import { alertFieldMap, ecsFieldMap, legacyAlertFieldMap } from '@kbn/alerts-as-data-utils'; import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; import { @@ -58,6 +58,7 @@ interface AlertsServiceParams { elasticsearchClientPromise: Promise; timeoutMs?: number; dataStreamAdapter: DataStreamAdapter; + elasticsearchAndSOAvailability$: Observable; } export interface CreateAlertsClientParams extends LegacyAlertsClientParams { @@ -131,7 +132,10 @@ export class AlertsService implements IAlertsService { this.dataStreamAdapter = options.dataStreamAdapter; // Kick off initialization of common assets and save the promise - this.commonInitPromise = this.initializeCommon(this.options.timeoutMs); + this.commonInitPromise = this.initializeCommon( + this.options.elasticsearchAndSOAvailability$, + this.options.timeoutMs + ); // Create helper for initializing context-specific resources this.resourceInitializationHelper = createResourceInstallationHelper( @@ -181,7 +185,10 @@ export class AlertsService implements IAlertsService { if (!this.initialized) { if (!this.isInitializing) { this.options.logger.info(`Retrying common resource initialization`); - initPromise = this.initializeCommon(this.options.timeoutMs); + initPromise = this.initializeCommon( + this.options.elasticsearchAndSOAvailability$, + this.options.timeoutMs + ); } else { this.options.logger.info( `Skipped retrying common resource initialization because it is already being retried.` @@ -295,8 +302,16 @@ export class AlertsService implements IAlertsService { * - ILM policy - common policy shared by all AAD indices * - Component template - common mappings for fields populated and used by the framework */ - private async initializeCommon(timeoutMs?: number): Promise { + private async initializeCommon( + elasticsearchAndSOAvailability$: Observable, + timeoutMs?: number + ): Promise { this.isInitializing = true; + // Wait to install resources until ES is ready + await firstValueFrom( + elasticsearchAndSOAvailability$.pipe(filter((areESAndSOAvailable) => areESAndSOAvailable)) + ); + try { this.options.logger.debug(`Initializing resources for AlertsService`); const esClient = await this.options.elasticsearchClientPromise; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index d310c6e3567c3..447dab2b94715 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -6,7 +6,14 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { BehaviorSubject, ReplaySubject, Subject } from 'rxjs'; +import { + BehaviorSubject, + ReplaySubject, + Subject, + Observable, + map, + distinctUntilChanged, +} from 'rxjs'; import { pick } from 'lodash'; import { UsageCollectionSetup, UsageCounter } from '@kbn/usage-collection-plugin/server'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; @@ -33,6 +40,7 @@ import { ServiceStatus, SavedObjectsBulkGetObject, ServiceStatusLevels, + CoreStatus, } from '@kbn/core/server'; import { LICENSE_TYPE, @@ -254,6 +262,8 @@ export class AlertingPlugin { this.licenseState = new LicenseState(plugins.licensing.license$); this.security = plugins.security; + const elasticsearchAndSOAvailability$ = getElasticsearchAndSOAvailability(core.status.core$); + const useDataStreamForAlerts = !!plugins.serverless; this.dataStreamAdapter = getDataStreamAdapter({ useDataStreamForAlerts }); @@ -315,6 +325,7 @@ export class AlertingPlugin { elasticsearchClientPromise: core .getStartServices() .then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser), + elasticsearchAndSOAvailability$, }); } } @@ -677,3 +688,16 @@ export class AlertingPlugin { this.pluginStop$.complete(); } } + +export function getElasticsearchAndSOAvailability( + core$: Observable +): Observable { + return core$.pipe( + map( + ({ elasticsearch, savedObjects }) => + elasticsearch.level === ServiceStatusLevels.available && + savedObjects.level === ServiceStatusLevels.available + ), + distinctUntilChanged() + ); +} diff --git a/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts index 84181bb512a78..9b65f6613baaf 100644 --- a/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/ad_hoc_task_runner.test.ts @@ -47,7 +47,7 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e import { alertsMock } from '../mocks'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { AlertsService } from '../alerts_service'; -import { ReplaySubject } from 'rxjs'; +import { of, ReplaySubject } from 'rxjs'; import { getDataStreamAdapter } from '../alerts_service/lib/data_stream_adapter'; import { AlertInstanceContext, @@ -124,12 +124,14 @@ type TaskRunnerFactoryInitializerParamsType = jest.Mocked & { const clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const alertingEventLogger = alertingEventLoggerMock.create(); +const elasticsearchAndSOAvailability$ = of(true); const alertsService = new AlertsService({ logger, pluginStop$: new ReplaySubject(1), kibanaVersion: '8.8.0', elasticsearchClientPromise: Promise.resolve(clusterClient), dataStreamAdapter: getDataStreamAdapter({ useDataStreamForAlerts }), + elasticsearchAndSOAvailability$, }); const backfillClient = backfillClientMock.create(); const dataPlugin = dataPluginMock.createStartContract(); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index d5d071208e398..4739ec5a91d3b 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -220,6 +220,7 @@ describe('Task Runner', () => { beforeEach(() => { jest.resetAllMocks(); + jest.restoreAllMocks(); // clear spy mock implementations logger.isLevelEnabled.mockReturnValue(true); jest .requireMock('../lib/wrap_scoped_cluster_client') @@ -1969,7 +1970,7 @@ describe('Task Runner', () => { }); test('should set unexpected errors as framework-error', async () => { - (getExecutorServicesModule.getExecutorServices as jest.Mock).mockImplementation(() => { + jest.spyOn(getExecutorServicesModule, 'getExecutorServices').mockImplementation(() => { throw new Error('test'); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts index 9f5ad725465e7..851bdddaed62a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts @@ -65,7 +65,7 @@ import * as RuleRunMetricsStoreModule from '../lib/rule_run_metrics_store'; import { legacyAlertsClientMock } from '../alerts_client/legacy_alerts_client.mock'; import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock'; import { AlertsService } from '../alerts_service'; -import { ReplaySubject } from 'rxjs'; +import { ReplaySubject, Subject } from 'rxjs'; import { IAlertsClient } from '../alerts_client/types'; import { getDataStreamAdapter } from '../alerts_service/lib/data_stream_adapter'; import { @@ -180,6 +180,7 @@ describe('Task Runner', () => { const ruleRunMetricsStore = ruleRunMetricsStoreMock.create(); const maintenanceWindowClient = maintenanceWindowClientMock.create(); const connectorAdapterRegistry = new ConnectorAdapterRegistry(); + const elasticsearchAndSOAvailability$ = new Subject(); type TaskRunnerFactoryInitializerParamsType = jest.Mocked & { actionsPlugin: jest.Mocked; @@ -387,7 +388,10 @@ describe('Task Runner', () => { kibanaVersion: '8.8.0', elasticsearchClientPromise: Promise.resolve(clusterClient), dataStreamAdapter: getDataStreamAdapter({ useDataStreamForAlerts }), + elasticsearchAndSOAvailability$, }); + elasticsearchAndSOAvailability$.next(true); + const spy = jest .spyOn(alertsService, 'getContextInitializationPromise') .mockResolvedValue({ result: true }); @@ -446,12 +450,12 @@ describe('Task Runner', () => { expect(logger.debug).toHaveBeenCalledTimes(useDataStreamForAlerts ? 9 : 10); let debugCall = 1; - expect(logger.debug).nthCalledWith(debugCall++, `Initializing resources for AlertsService`); expect(logger.debug).nthCalledWith( debugCall++, 'executing rule test:1 at 1970-01-01T00:00:00.000Z', { tags: ['1', 'test'] } ); + expect(logger.debug).nthCalledWith(debugCall++, `Initializing resources for AlertsService`); if (!useDataStreamForAlerts) { expect(logger.debug).nthCalledWith( @@ -516,7 +520,10 @@ describe('Task Runner', () => { kibanaVersion: '8.8.0', elasticsearchClientPromise: Promise.resolve(clusterClient), dataStreamAdapter: getDataStreamAdapter({ useDataStreamForAlerts }), + elasticsearchAndSOAvailability$, }); + elasticsearchAndSOAvailability$.next(true); + const spy = jest .spyOn(alertsService, 'getContextInitializationPromise') .mockResolvedValue({ result: true }); diff --git a/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx index 8251e0a1f18fa..d063c2fea3421 100644 --- a/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx @@ -6,9 +6,7 @@ */ import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; +import { screen, waitFor, render } from '@testing-library/react'; import { Severity } from './severity'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -16,28 +14,21 @@ import { FormTestComponent } from '../../common/test_utils'; const onSubmit = jest.fn(); -// FLAKY: https://github.com/elastic/kibana/issues/188951 -describe.skip('Severity form field', () => { - let appMockRender: AppMockRenderer; - - beforeEach(() => { - appMockRender = createAppMockRenderer(); - }); - +describe('Severity form field', () => { it('renders', async () => { - appMockRender.render( + render( ); expect(await screen.findByTestId('caseSeverity')).toBeInTheDocument(); - expect(await screen.findByTestId('case-severity-selection')).not.toHaveAttribute('disabled'); + expect(await screen.findByTestId('case-severity-selection')).toBeEnabled(); }); // default to LOW in this test configuration it('defaults to the correct value', async () => { - appMockRender.render( + render( @@ -48,7 +39,7 @@ describe.skip('Severity form field', () => { }); it('selects the correct value when changed', async () => { - appMockRender.render( + render( @@ -70,12 +61,12 @@ describe.skip('Severity form field', () => { }); it('disables when loading data', async () => { - appMockRender.render( + render( ); - expect(await screen.findByTestId('case-severity-selection')).toHaveAttribute('disabled'); + expect(await screen.findByTestId('case-severity-selection')).toBeDisabled(); }); }); diff --git a/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts index d01aac0c57577..18b3bf1268e26 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts @@ -7,13 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../../common/constants'; -import { PosturePolicyTemplate } from '../../../common/types_old'; -import type { - CspBenchmarksPage, - CspIntegrationDocNavigationItem, - CspPage, - CspPageNavigationItem, -} from './types'; +import type { CspBenchmarksPage, CspPage, CspPageNavigationItem } from './types'; const NAV_ITEMS_NAMES = { DASHBOARD: i18n.translate('xpack.csp.navigation.dashboardNavItemLabel', { @@ -116,10 +110,7 @@ export const findingsNavigation = { const ELASTIC_BASE_SHORT_URL = 'https://ela.st'; -export const cspIntegrationDocsNavigation: Record< - PosturePolicyTemplate, - CspIntegrationDocNavigationItem -> = { +export const cspIntegrationDocsNavigation = { kspm: { overviewPath: `${ELASTIC_BASE_SHORT_URL}/${KSPM_POLICY_TEMPLATE}`, getStartedPath: `${ELASTIC_BASE_SHORT_URL}/${KSPM_POLICY_TEMPLATE}-get-started`, @@ -127,5 +118,8 @@ export const cspIntegrationDocsNavigation: Record< cspm: { overviewPath: `${ELASTIC_BASE_SHORT_URL}/${CSPM_POLICY_TEMPLATE}`, getStartedPath: `${ELASTIC_BASE_SHORT_URL}/${CSPM_POLICY_TEMPLATE}-get-started`, + awsGetStartedPath: `https://www.elastic.co/guide/en/security/current/cspm-get-started.html`, + gcpGetStartedPath: `https://www.elastic.co/guide/en/security/current/cspm-get-started-gcp.html`, + azureGetStartedPath: `https://www.elastic.co/guide/en/security/current/cspm-get-started-azure.html`, }, }; diff --git a/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts b/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts index 8f4cbabc9f9ba..f436558e085d9 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts @@ -35,8 +35,3 @@ export type CloudSecurityPosturePageId = | 'cloud_security_posture-findings' | 'cloud_security_posture-benchmarks' | 'cloud_security_posture-benchmarks-rules'; - -export interface CspIntegrationDocNavigationItem { - overviewPath: string; - getStartedPath: string; -} diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx index 745540d5a4b0b..42c775fad3006 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx @@ -216,7 +216,7 @@ export const AwsCredentialsForm = ({ setupFormat, group, fields, - integrationLink, + elasticDocLink, hasCloudFormationTemplate, onSetupFormatChange, } = useAwsCredentialsForm({ @@ -237,7 +237,7 @@ export const AwsCredentialsForm = ({ defaultMessage="Utilize AWS CloudFormation (a built-in AWS tool) or a series of manual steps to set up and deploy CSPM for assessing your AWS environment's security posture. Refer to our {gettingStartedLink} guide for details." values={{ gettingStartedLink: ( - + {group.info} - + + - + ); }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts index 5c9603ee17cc5..0e562c17b552a 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts @@ -87,7 +87,7 @@ export const useAwsCredentialsForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [setupFormat, input.type]); - const integrationLink = cspIntegrationDocsNavigation.cspm.getStartedPath; + const elasticDocLink = cspIntegrationDocsNavigation.cspm.awsGetStartedPath; useCloudFormationTemplate({ packageInfo, @@ -136,7 +136,7 @@ export const useAwsCredentialsForm = ({ setupFormat, group, fields, - integrationLink, + elasticDocLink, hasCloudFormationTemplate, onSetupFormatChange, }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx index 2b0aaec9bb8af..25f7be8c8f4ee 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx @@ -38,7 +38,7 @@ import { CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS } from '../../test_subjects'; import { AZURE_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ } from '../../test_subjects'; interface AzureSetupInfoContentProps { - integrationLink: string; + documentationLink: string; } export type SetupFormat = typeof AZURE_SETUP_FORMAT.ARM_TEMPLATE | typeof AZURE_SETUP_FORMAT.MANUAL; @@ -58,7 +58,7 @@ export const AZURE_CREDENTIALS_TYPE = { MANAGED_IDENTITY: 'managed_identity', } as const; -export const AzureSetupInfoContent = ({ integrationLink }: AzureSetupInfoContentProps) => { +export const AzureSetupInfoContent = ({ documentationLink }: AzureSetupInfoContentProps) => { return ( <> @@ -74,13 +74,13 @@ export const AzureSetupInfoContent = ({ integrationLink }: AzureSetupInfoContent + ), @@ -221,19 +221,19 @@ const AzureCredentialTypeSelector = ({ ); -const TemporaryManualSetup = ({ integrationLink }: { integrationLink: string }) => { +const TemporaryManualSetup = ({ documentationLink }: { documentationLink: string }) => { return ( <> + ), @@ -356,7 +356,7 @@ export const AzureCredentialsForm = ({ azureCredentialsType, setupFormat, onSetupFormatChange, - integrationLink, + documentationLink, hasArmTemplateUrl, } = useAzureCredentialsForm({ newPolicy, @@ -410,7 +410,7 @@ export const AzureCredentialsForm = ({ return ( <> - + )} {setupFormat === AZURE_SETUP_FORMAT.MANUAL && !isPackageVersionValidForManualFields && ( - + )} {setupFormat === AZURE_SETUP_FORMAT.MANUAL && isPackageVersionValidForManualFields && ( <> diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx index dbe816e326f31..d8a88e5754864 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx @@ -31,14 +31,14 @@ export const AzureCredentialsFormAgentless = ({ updatePolicy, packageInfo, }: AzureCredentialsFormProps) => { - const integrationLink = cspIntegrationDocsNavigation.cspm.getStartedPath; + const documentationLink = cspIntegrationDocsNavigation.cspm.azureGetStartedPath; const options = getAzureCredentialsFormOptions(); const group = options[AZURE_CREDENTIALS_TYPE.SERVICE_PRINCIPAL_WITH_CLIENT_SECRET]; const fields = getInputVarsFields(input, group.fields); return ( <> - + + + + ), + }} /> ) : ( + + + ), + }} /> )} @@ -510,7 +531,7 @@ export const GcpCredentialsForm = ({ )} - + ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx index 6712b332e20b0..9cced3c87729b 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx @@ -252,7 +252,7 @@ export const GcpCredentialsFormAgentless = ({ packageInfo={packageInfo} /> - + ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx index 76134c4d41df0..7590e998cd0c2 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx @@ -917,7 +917,7 @@ describe('', () => { expect(getByText('Getting Started')).toHaveAttribute( 'href', - 'https://ela.st/cspm-get-started' + 'https://www.elastic.co/guide/en/security/current/cspm-get-started.html' ); }); @@ -934,7 +934,7 @@ describe('', () => { expect(getByTestId('externalLink')).toHaveAttribute( 'href', - 'https://ela.st/cspm-get-started' + 'https://www.elastic.co/guide/en/security/current/cspm-get-started.html' ); }); @@ -1224,7 +1224,10 @@ describe('', () => { ); - expect(getByText('documentation')).toHaveAttribute('href', 'https://ela.st/cspm-get-started'); + expect(getByText('documentation')).toHaveAttribute( + 'href', + 'https://www.elastic.co/guide/en/security/current/cspm-get-started-gcp.html' + ); }); it(`renders Google Cloud Shell forms when Setup Access is set to Google Cloud Shell`, () => { diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts index ac97411a5b92f..5b9155a756645 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts @@ -8,6 +8,8 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { PackagePolicy } from '../../../common'; + import { appContextService } from '..'; import type { MockedFleetAppContext } from '../../mocks'; @@ -24,7 +26,6 @@ import { } from '.'; jest.mock('../agent_policy'); -jest.mock('../package_policy'); jest.mock('../agents'); const mockedAgentPolicyService = agentPolicyService as jest.Mocked; @@ -54,7 +55,8 @@ describe('checkFleetServerVersionsForSecretsStorage', () => { it('should return true if all fleet server versions are at least the specified version and there are no managed policies', async () => { const version = '1.0.0'; - mockedPackagePolicyService.list + jest + .spyOn(mockedPackagePolicyService, 'list') .mockResolvedValueOnce({ items: [ { @@ -162,7 +164,7 @@ describe('getFleetServerPolicies', () => { policy_id: 'agent-policy-2', policy_ids: ['agent-policy-2'], }, - ]; + ] as PackagePolicy[]; const mockFleetServerPolicies = [ { id: 'fs-policy-1', @@ -185,16 +187,22 @@ describe('getFleetServerPolicies', () => { ]; it('should return no policies if there are no fleet server package policies', async () => { - (mockedPackagePolicyService.list as jest.Mock).mockResolvedValueOnce({ + jest.spyOn(mockedPackagePolicyService, 'list').mockResolvedValueOnce({ items: [], + total: 0, + page: 1, + perPage: 10, }); const result = await getFleetServerPolicies(soClient); expect(result).toEqual([]); }); it('should return agent policies with fleet server package policies', async () => { - (mockedPackagePolicyService.list as jest.Mock).mockResolvedValueOnce({ + jest.spyOn(mockedPackagePolicyService, 'list').mockResolvedValueOnce({ items: mockPackagePolicies, + total: mockPackagePolicies.length, + page: 1, + perPage: mockPackagePolicies.length, }); (mockedAgentPolicyService.getByIDs as jest.Mock).mockResolvedValueOnce(mockFleetServerPolicies); const result = await getFleetServerPolicies(soClient); diff --git a/x-pack/plugins/inference/common/chat_complete/index.ts b/x-pack/plugins/inference/common/chat_complete/index.ts index 175f86f74b5c4..abad6a4372595 100644 --- a/x-pack/plugins/inference/common/chat_complete/index.ts +++ b/x-pack/plugins/inference/common/chat_complete/index.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import type { Observable } from 'rxjs'; import type { InferenceTaskEventBase } from '../tasks'; import type { ToolCall, ToolCallsOf, ToolOptions } from './tools'; diff --git a/x-pack/plugins/inference/common/connectors.ts b/x-pack/plugins/inference/common/connectors.ts index 82baea2f83c39..f7ad616741d79 100644 --- a/x-pack/plugins/inference/common/connectors.ts +++ b/x-pack/plugins/inference/common/connectors.ts @@ -11,6 +11,8 @@ export enum InferenceConnectorType { Gemini = '.gemini', } +const allSupportedConnectorTypes = Object.values(InferenceConnectorType); + export interface InferenceConnector { type: InferenceConnectorType; name: string; @@ -18,9 +20,9 @@ export interface InferenceConnector { } export function isSupportedConnectorType(id: string): id is InferenceConnectorType { - return ( - id === InferenceConnectorType.OpenAI || - id === InferenceConnectorType.Bedrock || - id === InferenceConnectorType.Gemini - ); + return allSupportedConnectorTypes.includes(id as InferenceConnectorType); +} + +export interface GetConnectorsResponseBody { + connectors: InferenceConnector[]; } diff --git a/x-pack/plugins/inference/public/chat_complete/index.ts b/x-pack/plugins/inference/public/chat_complete/index.ts index 2509ea2dc1222..3dfe4616b7323 100644 --- a/x-pack/plugins/inference/public/chat_complete/index.ts +++ b/x-pack/plugins/inference/public/chat_complete/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { HttpStart } from '@kbn/core/public'; import { from } from 'rxjs'; -import { ChatCompleteAPI } from '../../common/chat_complete'; +import type { HttpStart } from '@kbn/core/public'; +import type { ChatCompleteAPI } from '../../common/chat_complete'; import type { ChatCompleteRequestBody } from '../../common/chat_complete/request'; import { httpResponseIntoObservable } from '../util/http_response_into_observable'; diff --git a/x-pack/plugins/inference/public/plugin.tsx b/x-pack/plugins/inference/public/plugin.tsx index 9785efb7a8874..13ef4a0373845 100644 --- a/x-pack/plugins/inference/public/plugin.tsx +++ b/x-pack/plugins/inference/public/plugin.tsx @@ -4,9 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; + +import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import type { Logger } from '@kbn/logging'; import { createOutputApi } from '../common/output/create_output_api'; +import type { GetConnectorsResponseBody } from '../common/connectors'; import { createChatCompleteApi } from './chat_complete'; import type { ConfigSchema, @@ -39,11 +41,15 @@ export class InferencePlugin start(coreStart: CoreStart, pluginsStart: InferenceStartDependencies): InferencePublicStart { const chatComplete = createChatCompleteApi({ http: coreStart.http }); + return { chatComplete, output: createOutputApi(chatComplete), - getConnectors: () => { - return coreStart.http.get('/internal/inference/connectors'); + getConnectors: async () => { + const res = await coreStart.http.get( + '/internal/inference/connectors' + ); + return res.connectors; }, }; } diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/get_inference_adapter.test.ts b/x-pack/plugins/inference/server/chat_complete/adapters/get_inference_adapter.test.ts new file mode 100644 index 0000000000000..272ad76538898 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/get_inference_adapter.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InferenceConnectorType } from '../../../common/connectors'; +import { getInferenceAdapter } from './get_inference_adapter'; +import { openAIAdapter } from './openai'; + +describe('getInferenceAdapter', () => { + it('returns the openAI adapter for OpenAI type', () => { + expect(getInferenceAdapter(InferenceConnectorType.OpenAI)).toBe(openAIAdapter); + }); + + it('returns undefined for Bedrock type', () => { + expect(getInferenceAdapter(InferenceConnectorType.Bedrock)).toBe(undefined); + }); + + it('returns undefined for Gemini type', () => { + expect(getInferenceAdapter(InferenceConnectorType.Gemini)).toBe(undefined); + }); +}); diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/get_inference_adapter.ts b/x-pack/plugins/inference/server/chat_complete/adapters/get_inference_adapter.ts new file mode 100644 index 0000000000000..a62ec8b795608 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/get_inference_adapter.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InferenceConnectorType } from '../../../common/connectors'; +import type { InferenceConnectorAdapter } from '../types'; +import { openAIAdapter } from './openai'; + +export const getInferenceAdapter = ( + connectorType: InferenceConnectorType +): InferenceConnectorAdapter | undefined => { + switch (connectorType) { + case InferenceConnectorType.OpenAI: + return openAIAdapter; + + case InferenceConnectorType.Bedrock: + // not implemented yet + break; + + case InferenceConnectorType.Gemini: + // not implemented yet + break; + } + + return undefined; +}; diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/index.ts b/x-pack/plugins/inference/server/chat_complete/adapters/index.ts new file mode 100644 index 0000000000000..bc420bdf57473 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getInferenceAdapter } from './get_inference_adapter'; diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.test.ts b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.test.ts index 7f55f8a8faa48..f3b7c423ea42f 100644 --- a/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.test.ts +++ b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.test.ts @@ -6,14 +6,14 @@ */ import OpenAI from 'openai'; -import { openAIAdapter } from '.'; -import type { ActionsClient } from '@kbn/actions-plugin/server/actions_client'; -import { ChatCompletionEventType, MessageRole } from '../../../../common/chat_complete'; +import { v4 } from 'uuid'; import { PassThrough } from 'stream'; import { pick } from 'lodash'; import { lastValueFrom, Subject, toArray } from 'rxjs'; +import { ChatCompletionEventType, MessageRole } from '../../../../common/chat_complete'; import { observableIntoEventSourceStream } from '../../../util/observable_into_event_source_stream'; -import { v4 } from 'uuid'; +import { InferenceExecutor } from '../../utils/inference_executor'; +import { openAIAdapter } from '.'; function createOpenAIChunk({ delta, @@ -39,38 +39,27 @@ function createOpenAIChunk({ } describe('openAIAdapter', () => { - const actionsClientMock = { - execute: jest.fn(), - } as ActionsClient & { execute: jest.MockedFn }; + const executorMock = { + invoke: jest.fn(), + } as InferenceExecutor & { invoke: jest.MockedFn }; beforeEach(() => { - actionsClientMock.execute.mockReset(); + executorMock.invoke.mockReset(); }); const defaultArgs = { - connector: { - id: 'foo', - actionTypeId: '.gen-ai', - name: 'OpenAI', - isPreconfigured: false, - isDeprecated: false, - isSystemAction: false, - }, - actionsClient: actionsClientMock, + executor: executorMock, }; describe('when creating the request', () => { function getRequest() { - const params = actionsClientMock.execute.mock.calls[0][0].params.subActionParams as Record< - string, - any - >; + const params = executorMock.invoke.mock.calls[0][0].subActionParams as Record; return { stream: params.stream, body: JSON.parse(params.body) }; } beforeEach(() => { - actionsClientMock.execute.mockImplementation(async () => { + executorMock.invoke.mockImplementation(async () => { return { actionId: '', status: 'ok', @@ -262,7 +251,7 @@ describe('openAIAdapter', () => { beforeEach(() => { source$ = new Subject>(); - actionsClientMock.execute.mockImplementation(async () => { + executorMock.invoke.mockImplementation(async () => { return { actionId: '', status: 'ok', diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.ts b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.ts index c811ed9f400ea..80fa9bfb781f5 100644 --- a/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.ts +++ b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.ts @@ -21,60 +21,30 @@ import { Message, MessageRole, } from '../../../../common/chat_complete'; +import type { ToolOptions } from '../../../../common/chat_complete/tools'; import { createTokenLimitReachedError } from '../../../../common/chat_complete/errors'; import { createInferenceInternalError } from '../../../../common/errors'; +import { eventSourceStreamIntoObservable } from '../../../util/event_source_stream_into_observable'; import { InferenceConnectorAdapter } from '../../types'; -import { eventSourceStreamIntoObservable } from '../event_source_stream_into_observable'; export const openAIAdapter: InferenceConnectorAdapter = { - chatComplete: ({ connector, actionsClient, system, messages, toolChoice, tools }) => { - const openAIMessages = messagesToOpenAI({ system, messages }); - - const toolChoiceForOpenAI = - typeof toolChoice === 'string' - ? toolChoice - : toolChoice - ? { - function: { - name: toolChoice.function, - }, - type: 'function' as const, - } - : undefined; - + chatComplete: ({ executor, system, messages, toolChoice, tools }) => { const stream = true; const request: Omit & { model?: string } = { stream, - messages: openAIMessages, + messages: messagesToOpenAI({ system, messages }), + tool_choice: toolChoiceToOpenAI(toolChoice), + tools: toolsToOpenAI(tools), temperature: 0, - tool_choice: toolChoiceForOpenAI, - tools: tools - ? Object.entries(tools).map(([toolName, { description, schema }]) => { - return { - type: 'function', - function: { - name: toolName, - description, - parameters: (schema ?? { - type: 'object' as const, - properties: {}, - }) as unknown as Record, - }, - }; - }) - : undefined, }; return from( - actionsClient.execute({ - actionId: connector.id, - params: { - subAction: 'stream', - subActionParams: { - body: JSON.stringify(request), - stream, - }, + executor.invoke({ + subAction: 'stream', + subActionParams: { + body: JSON.stringify(request), + stream, }, }) ).pipe( @@ -125,6 +95,39 @@ export const openAIAdapter: InferenceConnectorAdapter = { }, }; +function toolsToOpenAI(tools: ToolOptions['tools']): OpenAI.ChatCompletionCreateParams['tools'] { + return tools + ? Object.entries(tools).map(([toolName, { description, schema }]) => { + return { + type: 'function', + function: { + name: toolName, + description, + parameters: (schema ?? { + type: 'object' as const, + properties: {}, + }) as unknown as Record, + }, + }; + }) + : undefined; +} + +function toolChoiceToOpenAI( + toolChoice: ToolOptions['toolChoice'] +): OpenAI.ChatCompletionCreateParams['tool_choice'] { + return typeof toolChoice === 'string' + ? toolChoice + : toolChoice + ? { + function: { + name: toolChoice.function, + }, + type: 'function' as const, + } + : undefined; +} + function messagesToOpenAI({ system, messages, diff --git a/x-pack/plugins/inference/server/chat_complete/api.ts b/x-pack/plugins/inference/server/chat_complete/api.ts new file mode 100644 index 0000000000000..17bf0e5214300 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/api.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaRequest } from '@kbn/core-http-server'; +import { defer, switchMap, throwError } from 'rxjs'; +import type { ChatCompleteAPI, ChatCompletionResponse } from '../../common/chat_complete'; +import { createInferenceRequestError } from '../../common/errors'; +import type { InferenceStartDependencies } from '../types'; +import { getConnectorById } from '../util/get_connector_by_id'; +import { getInferenceAdapter } from './adapters'; +import { createInferenceExecutor, chunksIntoMessage } from './utils'; + +export function createChatCompleteApi({ + request, + actions, +}: { + request: KibanaRequest; + actions: InferenceStartDependencies['actions']; +}) { + const chatCompleteAPI: ChatCompleteAPI = ({ + connectorId, + messages, + toolChoice, + tools, + system, + }): ChatCompletionResponse => { + return defer(async () => { + const actionsClient = await actions.getActionsClientWithRequest(request); + const connector = await getConnectorById({ connectorId, actionsClient }); + const executor = createInferenceExecutor({ actionsClient, connector }); + return { executor, connector }; + }).pipe( + switchMap(({ executor, connector }) => { + const connectorType = connector.type; + const inferenceAdapter = getInferenceAdapter(connectorType); + + if (!inferenceAdapter) { + return throwError(() => + createInferenceRequestError(`Adapter for type ${connectorType} not implemented`, 400) + ); + } + + return inferenceAdapter.chatComplete({ + system, + executor, + messages, + toolChoice, + tools, + }); + }), + chunksIntoMessage({ + toolChoice, + tools, + }) + ); + }; + + return chatCompleteAPI; +} diff --git a/x-pack/plugins/inference/server/chat_complete/index.ts b/x-pack/plugins/inference/server/chat_complete/index.ts index e30afb58ca25a..5273822aea5b2 100644 --- a/x-pack/plugins/inference/server/chat_complete/index.ts +++ b/x-pack/plugins/inference/server/chat_complete/index.ts @@ -5,69 +5,4 @@ * 2.0. */ -import type { KibanaRequest } from '@kbn/core-http-server'; -import { defer, switchMap, throwError } from 'rxjs'; -import type { ChatCompleteAPI, ChatCompletionResponse } from '../../common/chat_complete'; -import type { ToolOptions } from '../../common/chat_complete/tools'; -import { InferenceConnectorType } from '../../common/connectors'; -import { createInferenceRequestError } from '../../common/errors'; -import type { InferenceStartDependencies } from '../types'; -import { chunksIntoMessage } from './adapters/chunks_into_message'; -import { openAIAdapter } from './adapters/openai'; - -export function createChatCompleteApi({ - request, - actions, -}: { - request: KibanaRequest; - actions: InferenceStartDependencies['actions']; -}) { - const chatCompleteAPI: ChatCompleteAPI = ({ - connectorId, - messages, - toolChoice, - tools, - system, - }): ChatCompletionResponse => { - return defer(async () => { - const actionsClient = await actions.getActionsClientWithRequest(request); - - const connector = await actionsClient.get({ id: connectorId, throwIfSystemAction: true }); - - return { actionsClient, connector }; - }).pipe( - switchMap(({ actionsClient, connector }) => { - switch (connector.actionTypeId) { - case InferenceConnectorType.OpenAI: - return openAIAdapter.chatComplete({ - system, - connector, - actionsClient, - messages, - toolChoice, - tools, - }); - - case InferenceConnectorType.Bedrock: - break; - - case InferenceConnectorType.Gemini: - break; - } - - return throwError(() => - createInferenceRequestError( - `Adapter for type ${connector.actionTypeId} not implemented`, - 400 - ) - ); - }), - chunksIntoMessage({ - toolChoice, - tools, - }) - ); - }; - - return chatCompleteAPI; -} +export { createChatCompleteApi } from './api'; diff --git a/x-pack/plugins/inference/server/chat_complete/types.ts b/x-pack/plugins/inference/server/chat_complete/types.ts index 6c89df1498646..fff902f7e885e 100644 --- a/x-pack/plugins/inference/server/chat_complete/types.ts +++ b/x-pack/plugins/inference/server/chat_complete/types.ts @@ -5,21 +5,36 @@ * 2.0. */ -import type { ActionsClient } from '@kbn/actions-plugin/server'; import type { Observable } from 'rxjs'; import type { - ChatCompleteAPI, ChatCompletionChunkEvent, ChatCompletionTokenCountEvent, + Message, } from '../../common/chat_complete'; +import type { ToolOptions } from '../../common/chat_complete/tools'; +import type { InferenceExecutor } from './utils'; -type Connector = Awaited>; - +/** + * Adapter in charge of communicating with a specific inference connector + * and to convert inputs/outputs from/to the common chatComplete inference format. + * + * @internal + */ export interface InferenceConnectorAdapter { chatComplete: ( - options: Omit[0], 'connectorId'> & { - actionsClient: ActionsClient; - connector: Connector; - } - ) => Observable; + options: { + messages: Message[]; + system?: string; + executor: InferenceExecutor; + } & ToolOptions + ) => Observable; } + +/** + * Events that can be emitted by the observable returned from {@link InferenceConnectorAdapter.chatComplete} + * + * @internal + */ +export type InferenceConnectorAdapterChatCompleteEvent = + | ChatCompletionChunkEvent + | ChatCompletionTokenCountEvent; diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.test.ts b/x-pack/plugins/inference/server/chat_complete/utils/chunks_into_message.test.ts similarity index 100% rename from x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.test.ts rename to x-pack/plugins/inference/server/chat_complete/utils/chunks_into_message.test.ts diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.ts b/x-pack/plugins/inference/server/chat_complete/utils/chunks_into_message.ts similarity index 100% rename from x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.ts rename to x-pack/plugins/inference/server/chat_complete/utils/chunks_into_message.ts diff --git a/x-pack/plugins/inference/server/chat_complete/utils/index.ts b/x-pack/plugins/inference/server/chat_complete/utils/index.ts new file mode 100644 index 0000000000000..dea2ac65f4755 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/utils/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + createInferenceExecutor, + type InferenceInvokeOptions, + type InferenceInvokeResult, + type InferenceExecutor, +} from './inference_executor'; +export { chunksIntoMessage } from './chunks_into_message'; diff --git a/x-pack/plugins/inference/server/chat_complete/utils/inference_executor.test.ts b/x-pack/plugins/inference/server/chat_complete/utils/inference_executor.test.ts new file mode 100644 index 0000000000000..1821b553dd6a9 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/utils/inference_executor.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { actionsClientMock } from '@kbn/actions-plugin/server/mocks'; +import { InferenceConnector, InferenceConnectorType } from '../../../common/connectors'; +import { createInferenceExecutor, type InferenceExecutor } from './inference_executor'; + +describe('createInferenceExecutor', () => { + let actionsClient: ReturnType; + let executor: InferenceExecutor; + + const connector: InferenceConnector = { + connectorId: 'foo', + name: 'My Connector', + type: InferenceConnectorType.OpenAI, + }; + + beforeEach(() => { + actionsClient = actionsClientMock.create(); + executor = createInferenceExecutor({ actionsClient, connector }); + }); + + describe('#invoke()', () => { + it('calls `actionsClient.execute` with the right parameters', async () => { + await executor.invoke({ subAction: 'stream', subActionParams: { over: 9000 } }); + + expect(actionsClient.execute).toHaveBeenCalledTimes(1); + expect(actionsClient.execute).toHaveBeenCalledWith({ + actionId: connector.connectorId, + params: { subAction: 'stream', subActionParams: { over: 9000 } }, + }); + }); + + it('returns the value returned from `actionsClient.execute`', async () => { + const expectedResult = Symbol.for('call_result'); + + actionsClient.execute.mockResolvedValue(expectedResult as any); + + const result = await executor.invoke({ + subAction: 'stream', + subActionParams: { over: 9000 }, + }); + + expect(result).toBe(expectedResult); + }); + }); +}); diff --git a/x-pack/plugins/inference/server/chat_complete/utils/inference_executor.ts b/x-pack/plugins/inference/server/chat_complete/utils/inference_executor.ts new file mode 100644 index 0000000000000..736beb82aa685 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/utils/inference_executor.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { InferenceConnector } from '../../../common/connectors'; + +export interface InferenceInvokeOptions { + subAction: string; + subActionParams?: Record; +} + +export type InferenceInvokeResult = ActionTypeExecutorResult; + +/** + * Represent the actual interface to communicate with the inference model. + * + * In practice, for now it's just a thin abstraction around the action client. + */ +export interface InferenceExecutor { + invoke(params: InferenceInvokeOptions): Promise; +} + +export const createInferenceExecutor = ({ + connector, + actionsClient, +}: { + connector: InferenceConnector; + actionsClient: ActionsClient; +}): InferenceExecutor => { + return { + async invoke({ subAction, subActionParams }): Promise { + return await actionsClient.execute({ + actionId: connector.connectorId, + params: { + subAction, + subActionParams, + }, + }); + }, + }; +}; diff --git a/x-pack/plugins/inference/server/inference_client/index.ts b/x-pack/plugins/inference/server/inference_client/index.ts index 3c25cf29f6280..d9d52a8e41ec1 100644 --- a/x-pack/plugins/inference/server/inference_client/index.ts +++ b/x-pack/plugins/inference/server/inference_client/index.ts @@ -6,12 +6,10 @@ */ import type { KibanaRequest } from '@kbn/core-http-server'; -import { ActionsClient } from '@kbn/actions-plugin/server'; -import { isSupportedConnectorType } from '../../common/connectors'; -import { createInferenceRequestError } from '../../common/errors'; -import { createChatCompleteApi } from '../chat_complete'; import type { InferenceClient, InferenceStartDependencies } from '../types'; +import { createChatCompleteApi } from '../chat_complete'; import { createOutputApi } from '../../common/output/create_output_api'; +import { getConnectorById } from '../util/get_connector_by_id'; export function createInferenceClient({ request, @@ -21,33 +19,9 @@ export function createInferenceClient({ return { chatComplete, output: createOutputApi(chatComplete), - getConnectorById: async (id: string) => { + getConnectorById: async (connectorId: string) => { const actionsClient = await actions.getActionsClientWithRequest(request); - let connector: Awaited>; - - try { - connector = await actionsClient.get({ - id, - throwIfSystemAction: true, - }); - } catch (error) { - throw createInferenceRequestError(`No connector found for id ${id}`, 400); - } - - const actionTypeId = connector.id; - - if (!isSupportedConnectorType(actionTypeId)) { - throw createInferenceRequestError( - `Type ${actionTypeId} not recognized as a supported connector type`, - 400 - ); - } - - return { - connectorId: connector.id, - name: connector.name, - type: actionTypeId, - }; + return await getConnectorById({ connectorId, actionsClient }); }, }; } diff --git a/x-pack/plugins/inference/server/plugin.ts b/x-pack/plugins/inference/server/plugin.ts index 26c56209df8ce..1b17eb4a66d35 100644 --- a/x-pack/plugins/inference/server/plugin.ts +++ b/x-pack/plugins/inference/server/plugin.ts @@ -8,10 +8,9 @@ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { createInferenceClient } from './inference_client'; -import { registerChatCompleteRoute } from './routes/chat_complete'; -import { registerConnectorsRoute } from './routes/connectors'; +import { registerRoutes } from './routes'; +import type { InferenceConfig } from './config'; import type { - ConfigSchema, InferenceServerSetup, InferenceServerStart, InferenceSetupDependencies, @@ -29,7 +28,7 @@ export class InferencePlugin { logger: Logger; - constructor(context: PluginInitializerContext) { + constructor(context: PluginInitializerContext) { this.logger = context.logger.get(); } setup( @@ -38,15 +37,11 @@ export class InferencePlugin ): InferenceServerSetup { const router = coreSetup.http.createRouter(); - registerChatCompleteRoute({ + registerRoutes({ router, coreSetup, }); - registerConnectorsRoute({ - router, - coreSetup, - }); return {}; } diff --git a/x-pack/plugins/inference/server/routes/chat_complete.ts b/x-pack/plugins/inference/server/routes/chat_complete.ts index 6c840f80466c2..6b5aea7b71696 100644 --- a/x-pack/plugins/inference/server/routes/chat_complete.ts +++ b/x-pack/plugins/inference/server/routes/chat_complete.ts @@ -7,7 +7,6 @@ import { schema, Type } from '@kbn/config-schema'; import type { CoreSetup, IRouter, RequestHandlerContext } from '@kbn/core/server'; -import { isObservable } from 'rxjs'; import { MessageRole } from '../../common/chat_complete'; import type { ChatCompleteRequestBody } from '../../common/chat_complete/request'; import { ToolCall, ToolChoiceType } from '../../common/chat_complete/tools'; @@ -20,7 +19,7 @@ const toolCallSchema: Type = schema.arrayOf( toolCallId: schema.string(), function: schema.object({ name: schema.string(), - arguments: schema.maybe(schema.object({}, { unknowns: 'allow' })), + arguments: schema.maybe(schema.recordOf(schema.string(), schema.any())), }), }) ); @@ -57,7 +56,7 @@ const chatCompleteBodySchema: Type = schema.object({ schema.oneOf([ schema.object({ role: schema.literal(MessageRole.Assistant), - content: schema.string(), + content: schema.oneOf([schema.string(), schema.literal(null)]), toolCalls: toolCallSchema, }), schema.object({ @@ -68,7 +67,7 @@ const chatCompleteBodySchema: Type = schema.object({ schema.object({ role: schema.literal(MessageRole.Tool), toolCallId: schema.string(), - response: schema.object({}, { unknowns: 'allow' }), + response: schema.recordOf(schema.string(), schema.any()), }), ]) ), @@ -97,7 +96,7 @@ export function registerChatCompleteRoute({ const { connectorId, messages, system, toolChoice, tools } = request.body; - const chatCompleteResponse = await client.chatComplete({ + const chatCompleteResponse = client.chatComplete({ connectorId, messages, system, @@ -105,13 +104,9 @@ export function registerChatCompleteRoute({ tools, }); - if (isObservable(chatCompleteResponse)) { - return response.ok({ - body: observableIntoEventSourceStream(chatCompleteResponse), - }); - } - - return response.ok({ body: chatCompleteResponse }); + return response.ok({ + body: observableIntoEventSourceStream(chatCompleteResponse), + }); } ); } diff --git a/x-pack/plugins/inference/server/routes/connectors.ts b/x-pack/plugins/inference/server/routes/connectors.ts index 8c69b68d55f14..a03a393f133b1 100644 --- a/x-pack/plugins/inference/server/routes/connectors.ts +++ b/x-pack/plugins/inference/server/routes/connectors.ts @@ -6,7 +6,11 @@ */ import type { CoreSetup, IRouter, RequestHandlerContext } from '@kbn/core/server'; -import { InferenceConnector, InferenceConnectorType } from '../../common/connectors'; +import { + InferenceConnector, + InferenceConnectorType, + isSupportedConnectorType, +} from '../../common/connectors'; import type { InferenceServerStart, InferenceStartDependencies } from '../types'; export function registerConnectorsRoute({ @@ -32,14 +36,8 @@ export function registerConnectorsRoute({ includeSystemActions: false, }); - const connectorTypes: string[] = [ - InferenceConnectorType.OpenAI, - InferenceConnectorType.Bedrock, - InferenceConnectorType.Gemini, - ]; - const connectors: InferenceConnector[] = allConnectors - .filter((connector) => connectorTypes.includes(connector.actionTypeId)) + .filter((connector) => isSupportedConnectorType(connector.actionTypeId)) .map((connector) => { return { connectorId: connector.id, diff --git a/x-pack/plugins/inference/server/routes/index.ts b/x-pack/plugins/inference/server/routes/index.ts new file mode 100644 index 0000000000000..0f6891ace1223 --- /dev/null +++ b/x-pack/plugins/inference/server/routes/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, IRouter } from '@kbn/core/server'; +import type { InferenceServerStart, InferenceStartDependencies } from '../types'; +import { registerChatCompleteRoute } from './chat_complete'; +import { registerConnectorsRoute } from './connectors'; + +export const registerRoutes = ({ + router, + coreSetup, +}: { + router: IRouter; + coreSetup: CoreSetup; +}) => { + registerChatCompleteRoute({ router, coreSetup }); + registerConnectorsRoute({ router, coreSetup }); +}; diff --git a/x-pack/plugins/inference/server/types.ts b/x-pack/plugins/inference/server/types.ts index 609b719b15236..20679ffd4cedf 100644 --- a/x-pack/plugins/inference/server/types.ts +++ b/x-pack/plugins/inference/server/types.ts @@ -16,8 +16,6 @@ import { OutputAPI } from '../common/output'; /* eslint-disable @typescript-eslint/no-empty-interface*/ -export interface ConfigSchema {} - export interface InferenceSetupDependencies { actions: ActionsPluginSetup; } diff --git a/x-pack/plugins/inference/server/util/event_source_stream_into_observable.test.ts b/x-pack/plugins/inference/server/util/event_source_stream_into_observable.test.ts new file mode 100644 index 0000000000000..235305a9316ce --- /dev/null +++ b/x-pack/plugins/inference/server/util/event_source_stream_into_observable.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Readable } from 'node:stream'; +import { toArray, firstValueFrom } from 'rxjs'; +import { eventSourceStreamIntoObservable } from './event_source_stream_into_observable'; + +describe('eventSourceStreamIntoObservable', () => { + it('emits for a single-chunk event', async () => { + const someMessage = JSON.stringify({ foo: 'bar' }); + const stream = Readable.from([`data: ${someMessage}\n\n`]); + + const results = await firstValueFrom(eventSourceStreamIntoObservable(stream).pipe(toArray())); + + expect(results).toEqual([someMessage]); + }); + + it('emits for single-chunk events', async () => { + const messages = [JSON.stringify({ foo: 'bar' }), '42', JSON.stringify({ foo: 'dolly' })]; + const stream = Readable.from(messages.map((message) => `data: ${message}\n\n`)); + + const results = await firstValueFrom(eventSourceStreamIntoObservable(stream).pipe(toArray())); + + expect(results).toEqual(messages); + }); + + it('emits for a multi-chunk event', async () => { + const stream = Readable.from([`data: abc`, `de`, `fgh\n\n`]); + + const results = await firstValueFrom(eventSourceStreamIntoObservable(stream).pipe(toArray())); + + expect(results).toEqual(['abcdefgh']); + }); + + it('emits for a multi-events chunk', async () => { + const stream = Readable.from([`data: A\n\ndata: B\n\ndata: C\n\n`]); + + const results = await firstValueFrom(eventSourceStreamIntoObservable(stream).pipe(toArray())); + + expect(results).toEqual(['A', 'B', 'C']); + }); + + it('emits for split chunk events', async () => { + const stream = Readable.from([`data: 42\n\ndata: `, `9000\n\nda`, `ta: 51\n\n`]); + + const results = await firstValueFrom(eventSourceStreamIntoObservable(stream).pipe(toArray())); + + expect(results).toEqual(['42', '9000', '51']); + }); +}); diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/event_source_stream_into_observable.ts b/x-pack/plugins/inference/server/util/event_source_stream_into_observable.ts similarity index 95% rename from x-pack/plugins/inference/server/chat_complete/adapters/event_source_stream_into_observable.ts rename to x-pack/plugins/inference/server/util/event_source_stream_into_observable.ts index ece32d76222cc..cad0a8e84d6a7 100644 --- a/x-pack/plugins/inference/server/chat_complete/adapters/event_source_stream_into_observable.ts +++ b/x-pack/plugins/inference/server/util/event_source_stream_into_observable.ts @@ -5,8 +5,8 @@ * 2.0. */ +import type { Readable } from 'node:stream'; import { createParser } from 'eventsource-parser'; -import { Readable } from 'node:stream'; import { Observable } from 'rxjs'; export function eventSourceStreamIntoObservable(readable: Readable) { diff --git a/x-pack/plugins/inference/server/util/get_connector_by_id.test.ts b/x-pack/plugins/inference/server/util/get_connector_by_id.test.ts new file mode 100644 index 0000000000000..7387944950f4a --- /dev/null +++ b/x-pack/plugins/inference/server/util/get_connector_by_id.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionResult as ActionConnector } from '@kbn/actions-plugin/server'; +import { actionsClientMock } from '@kbn/actions-plugin/server/mocks'; +import { InferenceConnectorType } from '../../common/connectors'; +import { getConnectorById } from './get_connector_by_id'; + +describe('getConnectorById', () => { + let actionsClient: ReturnType; + const connectorId = 'my-connector-id'; + + const createMockConnector = (parts: Partial = {}): ActionConnector => { + return { + id: 'mock', + name: 'Mock', + actionTypeId: 'action-type', + ...parts, + } as ActionConnector; + }; + + beforeEach(() => { + actionsClient = actionsClientMock.create(); + actionsClient.get.mockResolvedValue(createMockConnector()); + }); + + it('calls `actionsClient.get` with the right parameters', async () => { + actionsClient.get.mockResolvedValue( + createMockConnector({ + id: 'foo', + name: 'Foo', + actionTypeId: InferenceConnectorType.OpenAI, + }) + ); + + await getConnectorById({ actionsClient, connectorId }); + + expect(actionsClient.get).toHaveBeenCalledTimes(1); + expect(actionsClient.get).toHaveBeenCalledWith({ + id: connectorId, + throwIfSystemAction: true, + }); + }); + + it('throws if `actionsClient.get` throws', async () => { + actionsClient.get.mockImplementation(() => { + throw new Error('Something wrong'); + }); + + await expect(() => + getConnectorById({ actionsClient, connectorId }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"No connector found for id 'my-connector-id'"`); + }); + + it('throws the connector type is not compatible', async () => { + actionsClient.get.mockResolvedValue( + createMockConnector({ + id: 'tcp-pigeon-3-0', + name: 'Tcp Pigeon', + actionTypeId: '.tcp-pigeon', + }) + ); + + await expect(() => + getConnectorById({ actionsClient, connectorId }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Type '.tcp-pigeon' not recognized as a supported connector type"` + ); + }); + + it('returns the inference connector when successful', async () => { + actionsClient.get.mockResolvedValue( + createMockConnector({ + id: 'my-id', + name: 'My Name', + actionTypeId: InferenceConnectorType.OpenAI, + }) + ); + + const connector = await getConnectorById({ actionsClient, connectorId }); + + expect(connector).toEqual({ + connectorId: 'my-id', + name: 'My Name', + type: InferenceConnectorType.OpenAI, + }); + }); +}); diff --git a/x-pack/plugins/inference/server/util/get_connector_by_id.ts b/x-pack/plugins/inference/server/util/get_connector_by_id.ts new file mode 100644 index 0000000000000..3fd77630ad3d1 --- /dev/null +++ b/x-pack/plugins/inference/server/util/get_connector_by_id.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClient, ActionResult as ActionConnector } from '@kbn/actions-plugin/server'; +import { isSupportedConnectorType, type InferenceConnector } from '../../common/connectors'; +import { createInferenceRequestError } from '../../common/errors'; + +/** + * Retrieves a connector given the provided `connectorId` and asserts it's an inference connector + */ +export const getConnectorById = async ({ + connectorId, + actionsClient, +}: { + actionsClient: ActionsClient; + connectorId: string; +}): Promise => { + let connector: ActionConnector; + try { + connector = await actionsClient.get({ + id: connectorId, + throwIfSystemAction: true, + }); + } catch (error) { + throw createInferenceRequestError(`No connector found for id '${connectorId}'`, 400); + } + + const actionTypeId = connector.actionTypeId; + + if (!isSupportedConnectorType(actionTypeId)) { + throw createInferenceRequestError( + `Type '${actionTypeId}' not recognized as a supported connector type`, + 400 + ); + } + + return { + connectorId: connector.id, + name: connector.name, + type: actionTypeId, + }; +}; diff --git a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx index 94a17f85aad2c..d31981decb7e9 100644 --- a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx @@ -151,7 +151,7 @@ export const NodesList: FC = ({ compactView = false }) => { name: i18n.translate('xpack.ml.trainedModels.nodesList.nodeMemoryUsageHeader', { defaultMessage: 'Memory usage', }), - truncateText: true, + truncateText: false, 'data-test-subj': 'mlNodesTableColumnMemoryUsage', render: (v: NodeItem) => { return ; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/onboarding/introduction.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/onboarding/introduction.tsx index dfef9cd56050b..441d7a2f297f3 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/onboarding/introduction.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/onboarding/introduction.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import { EuiBetaBadge, EuiImage, EuiMarkdownFormat, EuiPageHeader } from '@elastic/eui'; +import { EuiBetaBadge, EuiImage, EuiPageHeader, EuiLink, EuiText } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useKibanaUrl } from '../../../hooks/use_kibana_url'; interface IntroductionProps { @@ -38,17 +39,6 @@ export function Introduction({ isBeta, guideLink }: IntroductionProps) { />, ]; - const description = i18n.translate('xpack.apm.onboarding.specProvider.longDescription', { - defaultMessage: - 'Application Performance Monitoring (APM) collects in-depth \ -performance metrics and errors from inside your application. \ -It allows you to monitor the performance of thousands of applications in real time. \ -[Learn more]({learnMoreLink}).', - values: { - learnMoreLink: guideLink, - }, - }); - return ( <> } - description={{description}} + description={ + + + {i18n.translate('xpack.apm.onboarding.specProvider.learnMoreLabel', { + defaultMessage: 'Learn more', + })} + + ), + }} + /> + + } rightSideItems={rightSideItems} /> diff --git a/x-pack/plugins/observability_solution/infra/public/containers/react_query_provider.tsx b/x-pack/plugins/observability_solution/infra/public/containers/react_query_provider.tsx index ee49e9cd081e0..cc47833ae3217 100644 --- a/x-pack/plugins/observability_solution/infra/public/containers/react_query_provider.tsx +++ b/x-pack/plugins/observability_solution/infra/public/containers/react_query_provider.tsx @@ -6,6 +6,7 @@ */ import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; import { QueryClient, QueryClientConfig, QueryClientProvider } from '@tanstack/react-query'; import merge from 'lodash/merge'; import { EuiButtonIcon } from '@elastic/eui'; @@ -36,7 +37,7 @@ export function ReactQueryProvider({ children, config = {} }: ProviderProps) { function HideableReactQueryDevTools() { const [isHidden, setIsHidden] = useState(false); - return !isHidden ? ( + return !isHidden && process.env.NODE_ENV === 'development' ? (
setIsHidden(!isHidden)} - aria-label="Disable React Query Dev Tools" + aria-label={i18n.translate( + 'xpack.infra.hideableReactQueryDevTools.euiButtonIcon.disableReactQueryDevLabel', + { defaultMessage: 'Disable React Query Dev Tools' } + )} />
diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts index f4da045101626..40f392084942b 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -392,7 +392,7 @@ describe('SyntheticsService', () => { describe('getSyntheticsParams', () => { it('returns the params for all spaces', async () => { const { service } = getMockedService(); - jest.spyOn(service, 'getSyntheticsParams').mockReset(); + jest.spyOn(service, 'getSyntheticsParams').mockRestore(); (axios as jest.MockedFunction).mockResolvedValue({} as AxiosResponse); @@ -416,7 +416,7 @@ describe('SyntheticsService', () => { it('returns the params for specific space', async () => { const { service } = getMockedService(); - jest.spyOn(service, 'getSyntheticsParams').mockReset(); + jest.spyOn(service, 'getSyntheticsParams').mockRestore(); serverMock.encryptedSavedObjects = mockEncryptedSO({ params: [ @@ -440,7 +440,7 @@ describe('SyntheticsService', () => { }); it('returns the space limited params', async () => { const { service } = getMockedService(); - jest.spyOn(service, 'getSyntheticsParams').mockReset(); + jest.spyOn(service, 'getSyntheticsParams').mockRestore(); serverMock.encryptedSavedObjects = mockEncryptedSO({ params: [ diff --git a/x-pack/plugins/reporting/server/lib/content_stream.test.ts b/x-pack/plugins/reporting/server/lib/content_stream.test.ts index 165c06baa579b..6d3fccff0b91a 100644 --- a/x-pack/plugins/reporting/server/lib/content_stream.test.ts +++ b/x-pack/plugins/reporting/server/lib/content_stream.test.ts @@ -44,6 +44,11 @@ describe('ContentStream', () => { ); }); + afterEach(() => { + stream.destroy(); + base64Stream.destroy(); + }); + describe('read', () => { it('should perform a search using index and the document id', async () => { await new Promise((resolve) => stream.once('data', resolve)); diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index 2a430c45b54d8..7f6b6e0bf6002 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { type Subject, ReplaySubject } from 'rxjs'; -import type { +import { type Subject, ReplaySubject, Observable, map, distinctUntilChanged } from 'rxjs'; +import { PluginInitializerContext, Plugin, CoreSetup, @@ -14,6 +14,8 @@ import type { KibanaRequest, CoreStart, IContextProvider, + CoreStatus, + ServiceStatusLevels, } from '@kbn/core/server'; import type { @@ -91,6 +93,8 @@ export class RuleRegistryPlugin ): RuleRegistryPluginSetupContract { const { logger, kibanaVersion } = this; + const elasticsearchAndSOAvailability$ = getElasticsearchAndSOAvailability(core.status.core$); + const startDependencies = core.getStartServices().then(([coreStart, pluginStart]) => { return { core: coreStart, @@ -115,6 +119,7 @@ export class RuleRegistryPlugin frameworkAlerts: plugins.alerting.frameworkAlerts, pluginStop$: this.pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); this.ruleDataService.initializeService(); @@ -200,3 +205,14 @@ export class RuleRegistryPlugin this.pluginStop$.complete(); } } + +function getElasticsearchAndSOAvailability(core$: Observable): Observable { + return core$.pipe( + map( + ({ elasticsearch, savedObjects }) => + elasticsearch.level === ServiceStatusLevels.available && + savedObjects.level === ServiceStatusLevels.available + ), + distinctUntilChanged() + ); +} diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts index 335ab73dd8fe6..d2011139adbb7 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type Subject, ReplaySubject } from 'rxjs'; +import { Subject, ReplaySubject, of } from 'rxjs'; import { ResourceInstaller } from './resource_installer'; import { loggerMock } from '@kbn/logging-mocks'; import { AlertConsumers } from '@kbn/rule-data-utils'; @@ -59,6 +59,7 @@ const GetDataStreamResponse: IndicesGetDataStreamResponse = { describe('resourceInstaller', () => { let pluginStop$: Subject; let dataStreamAdapter: DataStreamAdapter; + const elasticsearchAndSOAvailability$ = of(true); for (const useDataStreamForAlerts of [false, true]) { const label = useDataStreamForAlerts ? 'data streams' : 'aliases'; @@ -87,6 +88,7 @@ describe('resourceInstaller', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await installer.installCommonResources(); expect(getClusterClient).not.toHaveBeenCalled(); @@ -105,6 +107,7 @@ describe('resourceInstaller', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { feature: AlertConsumers.LOGS, @@ -137,6 +140,7 @@ describe('resourceInstaller', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await installer.installCommonResources(); @@ -155,6 +159,33 @@ describe('resourceInstaller', () => { ); }); + it('should not install common resources if ES is not ready', async () => { + const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); + const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient)); + const test$ = new Subject(); + + const installer = new ResourceInstaller({ + logger: loggerMock.create(), + isWriteEnabled: true, + disabledRegistrationContexts: [], + getResourceName: jest.fn(), + getClusterClient, + frameworkAlerts: frameworkAlertsService, + pluginStop$, + dataStreamAdapter, + elasticsearchAndSOAvailability$: test$, + }); + + const install = installer.installCommonResources(); + const timeout = new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + + await Promise.race([install, timeout]); + + expect(mockClusterClient.cluster.putComponentTemplate).not.toHaveBeenCalled(); + }); + it('should install subset of common resources when framework alerts are enabled', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient)); @@ -170,6 +201,7 @@ describe('resourceInstaller', () => { }, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); await installer.installCommonResources(); @@ -196,6 +228,7 @@ describe('resourceInstaller', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { @@ -232,6 +265,7 @@ describe('resourceInstaller', () => { }, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { @@ -281,6 +315,7 @@ describe('resourceInstaller', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { @@ -347,6 +382,7 @@ describe('resourceInstaller', () => { }, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { @@ -384,6 +420,7 @@ describe('resourceInstaller', () => { }, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { @@ -426,6 +463,7 @@ describe('resourceInstaller', () => { }, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { @@ -490,6 +528,7 @@ describe('resourceInstaller', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }; const indexOptions = { feature: AlertConsumers.OBSERVABILITY, diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts index f1dfb6f851eb0..2a6533d7e1002 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type Observable } from 'rxjs'; +import { type Observable, firstValueFrom, filter } from 'rxjs'; import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { @@ -37,6 +37,7 @@ interface ConstructorOptions { frameworkAlerts: PublicFrameworkAlertsService; pluginStop$: Observable; dataStreamAdapter: DataStreamAdapter; + elasticsearchAndSOAvailability$: Observable; } export type IResourceInstaller = PublicMethodsOf; @@ -53,6 +54,11 @@ export class ResourceInstaller { * - component template containing all standard ECS fields */ public async installCommonResources(): Promise { + await firstValueFrom( + this.options.elasticsearchAndSOAvailability$.pipe( + filter((areESAndSOAvailable) => areESAndSOAvailable) + ) + ); const resourceDescription = 'common resources shared between all indices'; const { logger, isWriteEnabled } = this.options; if (!isWriteEnabled) { diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.test.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.test.ts index b2736ee7f3cfe..1f36799fc65ec 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.test.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type Subject, ReplaySubject } from 'rxjs'; +import { type Subject, ReplaySubject, of } from 'rxjs'; import { loggerMock } from '@kbn/logging-mocks'; import { RuleDataService } from './rule_data_plugin_service'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; @@ -29,6 +29,7 @@ const frameworkAlertsService = { describe('ruleDataPluginService', () => { let pluginStop$: Subject; let dataStreamAdapter: DataStreamAdapter; + const elasticsearchAndSOAvailability$ = of(true); beforeEach(() => { jest.resetAllMocks(); @@ -56,6 +57,7 @@ describe('ruleDataPluginService', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); expect(ruleDataService.isRegistrationContextDisabled('observability.logs')).toBe(true); }); @@ -74,6 +76,7 @@ describe('ruleDataPluginService', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); expect(ruleDataService.isRegistrationContextDisabled('observability.apm')).toBe(false); }); @@ -94,6 +97,7 @@ describe('ruleDataPluginService', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); expect(ruleDataService.isWriteEnabled('observability.logs')).toBe(false); @@ -115,6 +119,7 @@ describe('ruleDataPluginService', () => { frameworkAlerts: frameworkAlertsService, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); const indexOptions = { feature: AlertConsumers.LOGS, diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts index a5f160f05c537..efe4a5dd4f32f 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/rule_data_plugin_service.ts @@ -99,6 +99,7 @@ interface ConstructorOptions { frameworkAlerts: PublicFrameworkAlertsService; pluginStop$: Observable; dataStreamAdapter: DataStreamAdapter; + elasticsearchAndSOAvailability$: Observable; } export class RuleDataService implements IRuleDataService { @@ -122,6 +123,7 @@ export class RuleDataService implements IRuleDataService { frameworkAlerts: options.frameworkAlerts, pluginStop$: options.pluginStop$, dataStreamAdapter: options.dataStreamAdapter, + elasticsearchAndSOAvailability$: this.options.elasticsearchAndSOAvailability$, }); this.installCommonResources = Promise.resolve(right('ok')); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.test.tsx index e342cbd388ad6..1d9be2b33ef7e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.test.tsx @@ -61,4 +61,16 @@ describe('Policy form SettingCard component', () => { expect(renderResult.getByTestId('test-rightCornerContainer')).not.toBeEmptyDOMElement(); expect(renderResult.getByTestId('test-rightContent')); }); + + it('should show right corner content in viewport width greater than 1600px', () => { + // Set the viewport above xxl breakpoint + window.innerWidth = 1601; + window.dispatchEvent(new Event('resize')); + + formProps.rightCorner =
{'foo'}
; + render(); + + const rightContent = renderResult.getByTestId('test-rightContent'); + expect(rightContent).toBeVisible(); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.tsx index 646c807e8376b..d460eaa068e38 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/setting_card.tsx @@ -127,7 +127,7 @@ export const SettingCard: FC = memo( )} - + diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts index 381a2dce89df8..2737c4f2d8085 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts @@ -36,13 +36,25 @@ describe('duplicateRule', () => { meta: undefined, maxSignals: 100, responseActions: [], - relatedIntegrations: [], - requiredFields: [], + relatedIntegrations: [ + { + package: 'aws', + version: '~1.2.3', + integration: 'route53', + }, + ], + requiredFields: [ + { + name: 'event.action', + type: 'keyword', + ecs: true, + }, + ], riskScore: 42, riskScoreMapping: [], severity: 'low', severityMapping: [], - setup: 'Some setup guide.', + setup: `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`, threat: [], to: 'now', references: [], @@ -94,106 +106,23 @@ describe('duplicateRule', () => { jest.clearAllMocks(); }); - it('returns an object with fields copied from a given rule', async () => { - const rule = createTestRule(); - const result = await duplicateRule({ - rule, - }); - - expect(result).toEqual({ - name: expect.anything(), // covered in a separate test - params: { - ...rule.params, - ruleSource: { - type: 'internal', - }, - ruleId: expect.anything(), // covered in a separate test - }, - tags: rule.tags, - alertTypeId: rule.alertTypeId, - consumer: rule.consumer, - schedule: rule.schedule, - actions: rule.actions, - systemActions: rule.actions, - enabled: false, // covered in a separate test - }); - }); - - it('appends [Duplicate] to the name', async () => { - const rule = createTestRule(); - rule.name = 'PowerShell Keylogging Script'; - const result = await duplicateRule({ - rule, - }); - - expect(result).toEqual( - expect.objectContaining({ - name: 'PowerShell Keylogging Script [Duplicate]', - }) - ); - }); - - it('generates a new ruleId', async () => { - const rule = createTestRule(); - const result = await duplicateRule({ - rule, - }); - - expect(result).toEqual( - expect.objectContaining({ - params: expect.objectContaining({ - ruleId: 'new ruleId', - }), - }) - ); - }); - - it('makes sure the duplicated rule is disabled', async () => { - const rule = createTestRule(); - rule.enabled = true; - const result = await duplicateRule({ - rule, - }); - - expect(result).toEqual( - expect.objectContaining({ - enabled: false, - }) - ); - }); - - describe('when duplicating a prebuilt (immutable) rule', () => { - const createPrebuiltRule = () => { + describe('when duplicating any kind of rule', () => { + it('appends [Duplicate] to the name', async () => { const rule = createTestRule(); - rule.params.immutable = true; - return rule; - }; - - it('transforms it to a custom (mutable) rule', async () => { - const rule = createPrebuiltRule(); + rule.name = 'PowerShell Keylogging Script'; const result = await duplicateRule({ rule, }); expect(result).toEqual( expect.objectContaining({ - params: expect.objectContaining({ - immutable: false, - }), + name: 'PowerShell Keylogging Script [Duplicate]', }) ); }); - it('resets related integrations to an empty array', async () => { - const rule = createPrebuiltRule(); - rule.params.relatedIntegrations = [ - { - package: 'aws', - version: '~1.2.3', - integration: 'route53', - }, - ]; - + it('generates a new ruleId', async () => { + const rule = createTestRule(); const result = await duplicateRule({ rule, }); @@ -201,45 +130,40 @@ describe('duplicateRule', () => { expect(result).toEqual( expect.objectContaining({ params: expect.objectContaining({ - relatedIntegrations: [], + ruleId: 'new ruleId', }), }) ); }); - it('resets required fields to an empty array', async () => { - const rule = createPrebuiltRule(); - rule.params.requiredFields = [ - { - name: 'event.action', - type: 'keyword', - ecs: true, - }, - ]; - + it('makes sure the duplicated rule is disabled', async () => { + const rule = createTestRule(); + rule.enabled = true; const result = await duplicateRule({ rule, }); expect(result).toEqual( expect.objectContaining({ - params: expect.objectContaining({ - requiredFields: [], - }), + enabled: false, }) ); }); }); - describe('when duplicating a custom (mutable) rule', () => { - const createCustomRule = () => { + describe('when duplicating a prebuilt rule', () => { + const createPrebuiltRule = () => { const rule = createTestRule(); - rule.params.immutable = false; + rule.params.immutable = true; + rule.params.ruleSource = { + type: 'external', + isCustomized: false, + }; return rule; }; - it('keeps it custom', async () => { - const rule = createCustomRule(); + it('transforms it to a custom rule', async () => { + const rule = createPrebuiltRule(); const result = await duplicateRule({ rule, }); @@ -248,44 +172,51 @@ describe('duplicateRule', () => { expect.objectContaining({ params: expect.objectContaining({ immutable: false, + ruleSource: { + type: 'internal', + }, }), }) ); }); - it('copies related integrations as is', async () => { - const rule = createCustomRule(); - rule.params.relatedIntegrations = [ - { - package: 'aws', - version: '~1.2.3', - integration: 'route53', - }, - ]; - + it('copies fields from the original rule', async () => { + const rule = createPrebuiltRule(); const result = await duplicateRule({ rule, }); - expect(result).toEqual( - expect.objectContaining({ - params: expect.objectContaining({ - relatedIntegrations: rule.params.relatedIntegrations, - }), - }) - ); + expect(result).toEqual({ + name: expect.anything(), // covered in a separate test + params: { + ...rule.params, + ruleId: expect.anything(), // covered in a separate test + immutable: expect.anything(), // covered in a separate test + ruleSource: expect.anything(), // covered in a separate test + }, + tags: rule.tags, + alertTypeId: rule.alertTypeId, + consumer: rule.consumer, + schedule: rule.schedule, + actions: rule.actions, + systemActions: rule.actions, + enabled: false, // covered in a separate test + }); }); + }); - it('copies required fields as is', async () => { - const rule = createCustomRule(); - rule.params.requiredFields = [ - { - name: 'event.action', - type: 'keyword', - ecs: true, - }, - ]; + describe('when duplicating a custom rule', () => { + const createCustomRule = () => { + const rule = createTestRule(); + rule.params.immutable = false; + rule.params.ruleSource = { + type: 'internal', + }; + return rule; + }; + it('keeps it custom', async () => { + const rule = createCustomRule(); const result = await duplicateRule({ rule, }); @@ -293,26 +224,35 @@ describe('duplicateRule', () => { expect(result).toEqual( expect.objectContaining({ params: expect.objectContaining({ - requiredFields: rule.params.requiredFields, + immutable: false, + ruleSource: { + type: 'internal', + }, }), }) ); }); - it('copies setup guide as is', async () => { + it('copies fields from the original rule', async () => { const rule = createCustomRule(); - rule.params.setup = `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`; const result = await duplicateRule({ rule, }); - expect(result).toEqual( - expect.objectContaining({ - params: expect.objectContaining({ - setup: rule.params.setup, - }), - }) - ); + expect(result).toEqual({ + name: expect.anything(), // covered in a separate test + params: { + ...rule.params, + ruleId: expect.anything(), // covered in a separate test + }, + tags: rule.tags, + alertTypeId: rule.alertTypeId, + consumer: rule.consumer, + schedule: rule.schedule, + actions: rule.actions, + systemActions: rule.actions, + enabled: false, // covered in a separate test + }); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts index 1324e2adb01ab..58c214950728d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts @@ -27,18 +27,18 @@ interface DuplicateRuleParams { export const duplicateRule = async ({ rule }: DuplicateRuleParams): Promise => { // Generate a new static ruleId - const ruleId = uuidv4(); - - // If it's a prebuilt rule, reset Related Integrations, Required Fields and Setup Guide. - // We do this because for now we don't allow the users to edit these fields for custom rules. - const isPrebuilt = rule.params.immutable; - const relatedIntegrations = isPrebuilt ? [] : rule.params.relatedIntegrations; - const requiredFields = isPrebuilt ? [] : rule.params.requiredFields; - - const actions = transformToActionFrequency(rule.actions, rule.throttle); + const ruleId: InternalRuleCreate['params']['ruleId'] = uuidv4(); // Duplicated rules are always considered custom rules - const immutable = false; + const immutable: InternalRuleCreate['params']['immutable'] = false; + const ruleSource: InternalRuleCreate['params']['ruleSource'] = { + type: 'internal', + }; + + const actions: InternalRuleCreate['actions'] = transformToActionFrequency( + rule.actions, + rule.throttle + ); return { name: `${rule.name} [${DUPLICATE_TITLE}]`, @@ -47,13 +47,9 @@ export const duplicateRule = async ({ rule }: DuplicateRuleParams): Promise { - for (const useDataStreamForAlerts of [false, true]) { - const label = useDataStreamForAlerts ? 'data streams' : 'aliases'; + describe.each(['data streams', 'aliases'])(`using %s for alert indices`, () => { + let riskEngineDataClient: RiskEngineDataClient; + let mockSavedObjectClient: ReturnType; + let logger: ReturnType; - describe(`using ${label} for alert indices`, () => { - let riskEngineDataClient: RiskEngineDataClient; - let mockSavedObjectClient: ReturnType; - let logger: ReturnType; + beforeEach(() => { const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser; + logger = loggingSystemMock.createLogger(); + mockSavedObjectClient = savedObjectsClientMock.create(); + const options = { + logger, + kibanaVersion: '8.9.0', + esClient, + soClient: mockSavedObjectClient, + namespace: 'default', + auditLogger: undefined, + }; + riskEngineDataClient = new RiskEngineDataClient(options); + }); - beforeEach(() => { - logger = loggingSystemMock.createLogger(); - mockSavedObjectClient = savedObjectsClientMock.create(); - const options = { - logger, - kibanaVersion: '8.9.0', - esClient, - soClient: mockSavedObjectClient, - namespace: 'default', - auditLogger: undefined, - }; - riskEngineDataClient = new RiskEngineDataClient(options); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - afterEach(() => { - jest.clearAllMocks(); - }); + afterAll(() => { + jest.restoreAllMocks(); + }); - describe('#getConfiguration', () => { - it('retrieves configuration from the saved object', async () => { - mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); + describe('#getConfiguration', () => { + it('retrieves configuration from the saved object', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); - const configuration = await riskEngineDataClient.getConfiguration(); + const configuration = await riskEngineDataClient.getConfiguration(); - expect(mockSavedObjectClient.find).toHaveBeenCalledTimes(1); + expect(mockSavedObjectClient.find).toHaveBeenCalledTimes(1); - expect(configuration).toEqual({ - enabled: false, - }); + expect(configuration).toEqual({ + enabled: false, }); }); + }); - describe('enableRiskEngine', () => { - let mockTaskManagerStart: ReturnType; + describe('enableRiskEngine', () => { + let mockTaskManagerStart: ReturnType; - beforeEach(() => { - mockSavedObjectClient.find.mockResolvedValue(getSavedObjectConfiguration()); - mockTaskManagerStart = taskManagerMock.createStart(); + beforeEach(() => { + mockSavedObjectClient.find.mockResolvedValue(getSavedObjectConfiguration()); + mockTaskManagerStart = taskManagerMock.createStart(); + }); + + it('returns an error if saved object does not exist', async () => { + mockSavedObjectClient.find.mockResolvedValue({ + page: 1, + per_page: 20, + total: 0, + saved_objects: [], }); - it('returns an error if saved object does not exist', async () => { - mockSavedObjectClient.find.mockResolvedValue({ - page: 1, - per_page: 20, - total: 0, - saved_objects: [], - }); + await expect( + riskEngineDataClient.enableRiskEngine({ taskManager: mockTaskManagerStart }) + ).rejects.toThrow('Risk engine configuration not found'); + }); - await expect( - riskEngineDataClient.enableRiskEngine({ taskManager: mockTaskManagerStart }) - ).rejects.toThrow('Risk engine configuration not found'); + it('should update saved object attribute', async () => { + await riskEngineDataClient.enableRiskEngine({ taskManager: mockTaskManagerStart }); + + expect(mockSavedObjectClient.update).toHaveBeenCalledWith( + 'risk-engine-configuration', + 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', + { + enabled: true, + }, + { + refresh: 'wait_for', + } + ); + }); + + describe('if task manager throws an error', () => { + beforeEach(() => { + mockTaskManagerStart.ensureScheduled.mockRejectedValueOnce( + new Error('Task Manager error') + ); }); - it('should update saved object attribute', async () => { - await riskEngineDataClient.enableRiskEngine({ taskManager: mockTaskManagerStart }); + it('disables the risk engine and re-throws the error', async () => { + await expect( + riskEngineDataClient.enableRiskEngine({ taskManager: mockTaskManagerStart }) + ).rejects.toThrow('Task Manager error'); expect(mockSavedObjectClient.update).toHaveBeenCalledWith( 'risk-engine-configuration', 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', { - enabled: true, + enabled: false, }, { refresh: 'wait_for', } ); }); - - describe('if task manager throws an error', () => { - beforeEach(() => { - mockTaskManagerStart.ensureScheduled.mockRejectedValueOnce( - new Error('Task Manager error') - ); - }); - - it('disables the risk engine and re-throws the error', async () => { - await expect( - riskEngineDataClient.enableRiskEngine({ taskManager: mockTaskManagerStart }) - ).rejects.toThrow('Task Manager error'); - - expect(mockSavedObjectClient.update).toHaveBeenCalledWith( - 'risk-engine-configuration', - 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', - { - enabled: false, - }, - { - refresh: 'wait_for', - } - ); - }); - }); }); + }); - describe('disableRiskEngine', () => { - let mockTaskManagerStart: ReturnType; + describe('disableRiskEngine', () => { + let mockTaskManagerStart: ReturnType; - beforeEach(() => { - mockTaskManagerStart = taskManagerMock.createStart(); - }); + beforeEach(() => { + mockTaskManagerStart = taskManagerMock.createStart(); + }); - it('should return error if saved object not exist', async () => { - mockSavedObjectClient.find.mockResolvedValueOnce({ - page: 1, - per_page: 20, - total: 0, - saved_objects: [], - }); - - expect.assertions(1); - try { - await riskEngineDataClient.disableRiskEngine({ taskManager: mockTaskManagerStart }); - } catch (e) { - expect(e.message).toEqual('Risk engine configuration not found'); - } + it('should return error if saved object not exist', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce({ + page: 1, + per_page: 20, + total: 0, + saved_objects: [], }); - it('should update saved object attrubute', async () => { - mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); - + expect.assertions(1); + try { await riskEngineDataClient.disableRiskEngine({ taskManager: mockTaskManagerStart }); + } catch (e) { + expect(e.message).toEqual('Risk engine configuration not found'); + } + }); - expect(mockSavedObjectClient.update).toHaveBeenCalledWith( - 'risk-engine-configuration', - 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', - { - enabled: false, - }, - { - refresh: 'wait_for', - } - ); - }); + it('should update saved object attribute', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); + + await riskEngineDataClient.disableRiskEngine({ taskManager: mockTaskManagerStart }); + + expect(mockSavedObjectClient.update).toHaveBeenCalledWith( + 'risk-engine-configuration', + 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', + { + enabled: false, + }, + { + refresh: 'wait_for', + } + ); }); + }); - describe('init', () => { - let mockTaskManagerStart: ReturnType; - const initRiskScore = jest.spyOn(RiskScoreDataClient.prototype, 'init'); - const enableRiskEngineMock = jest.spyOn(RiskEngineDataClient.prototype, 'enableRiskEngine'); + describe('init', () => { + let mockTaskManagerStart: ReturnType; + let initRiskScore: jest.SpyInstance; + let enableRiskEngineMock: jest.SpyInstance; + let disableLegacyRiskEngineMock: jest.SpyInstance; - const disableLegacyRiskEngineMock = jest.spyOn( + beforeEach(() => { + initRiskScore = jest.spyOn(RiskScoreDataClient.prototype, 'init'); + enableRiskEngineMock = jest.spyOn(RiskEngineDataClient.prototype, 'enableRiskEngine'); + disableLegacyRiskEngineMock = jest.spyOn( RiskEngineDataClient.prototype, 'disableLegacyRiskEngine' ); - beforeEach(() => { - mockTaskManagerStart = taskManagerMock.createStart(); - disableLegacyRiskEngineMock.mockImplementation(() => Promise.resolve(true)); - initRiskScore.mockImplementation(() => { - return Promise.resolve(); - }); + mockTaskManagerStart = taskManagerMock.createStart(); + disableLegacyRiskEngineMock.mockImplementation(() => Promise.resolve(true)); - enableRiskEngineMock.mockImplementation(() => { - return Promise.resolve(getSavedObjectConfiguration().saved_objects[0]); - }); + initRiskScore.mockImplementation(() => { + return Promise.resolve(); + }); - jest - .spyOn(savedObjectConfig, 'initSavedObjects') - .mockResolvedValue({} as unknown as SavedObject); + enableRiskEngineMock.mockImplementation(() => { + return Promise.resolve(getSavedObjectConfiguration().saved_objects[0]); }); - afterEach(() => { - initRiskScore.mockReset(); - enableRiskEngineMock.mockReset(); - disableLegacyRiskEngineMock.mockReset(); + jest + .spyOn(savedObjectConfig, 'initSavedObjects') + .mockResolvedValue({} as unknown as SavedObject); + }); + + afterEach(() => { + initRiskScore.mockReset(); + enableRiskEngineMock.mockReset(); + disableLegacyRiskEngineMock.mockReset(); + }); + + it('success', async () => { + const initResult = await riskEngineDataClient.init({ + namespace: 'default', + taskManager: mockTaskManagerStart, + riskScoreDataClient: riskScoreDataClientMock.create(), }); - it('success', async () => { - const initResult = await riskEngineDataClient.init({ - namespace: 'default', - taskManager: mockTaskManagerStart, - riskScoreDataClient: riskScoreDataClientMock.create(), - }); - - expect(initResult).toEqual({ - errors: [], - legacyRiskEngineDisabled: true, - riskEngineConfigurationCreated: true, - riskEngineEnabled: true, - riskEngineResourcesInstalled: true, - }); + expect(initResult).toEqual({ + errors: [], + legacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: true, + riskEngineEnabled: true, + riskEngineResourcesInstalled: true, }); + }); - it('should catch error for disableLegacyRiskEngine, but continue', async () => { - disableLegacyRiskEngineMock.mockImplementation(() => { - throw new Error('Error disableLegacyRiskEngineMock'); - }); - const initResult = await riskEngineDataClient.init({ - namespace: 'default', - taskManager: mockTaskManagerStart, - riskScoreDataClient: riskScoreDataClientMock.create(), - }); - - expect(initResult).toEqual({ - errors: ['Error disableLegacyRiskEngineMock'], - legacyRiskEngineDisabled: false, - riskEngineConfigurationCreated: true, - riskEngineEnabled: true, - riskEngineResourcesInstalled: true, - }); + it('should catch error for disableLegacyRiskEngine, but continue', async () => { + disableLegacyRiskEngineMock.mockImplementation(() => { + throw new Error('Error disableLegacyRiskEngineMock'); + }); + const initResult = await riskEngineDataClient.init({ + namespace: 'default', + taskManager: mockTaskManagerStart, + riskScoreDataClient: riskScoreDataClientMock.create(), }); - it('should catch error for resource init', async () => { - disableLegacyRiskEngineMock.mockImplementationOnce(() => { - throw new Error('Error disableLegacyRiskEngineMock'); - }); - - const initResult = await riskEngineDataClient.init({ - namespace: 'default', - taskManager: mockTaskManagerStart, - riskScoreDataClient: riskScoreDataClientMock.create(), - }); - - expect(initResult).toEqual({ - errors: ['Error disableLegacyRiskEngineMock'], - legacyRiskEngineDisabled: false, - riskEngineConfigurationCreated: true, - riskEngineEnabled: true, - riskEngineResourcesInstalled: true, - }); + expect(initResult).toEqual({ + errors: ['Error disableLegacyRiskEngineMock'], + legacyRiskEngineDisabled: false, + riskEngineConfigurationCreated: true, + riskEngineEnabled: true, + riskEngineResourcesInstalled: true, }); + }); - it('should catch error for initializeResources and stop', async () => { - const riskScoreDataClient = riskScoreDataClientMock.create(); - riskScoreDataClient.init.mockImplementationOnce(() => { - throw new Error('Error riskScoreDataClient'); - }); - - const initResult = await riskEngineDataClient.init({ - namespace: 'default', - taskManager: mockTaskManagerStart, - riskScoreDataClient, - }); - - expect(initResult).toEqual({ - errors: ['Error riskScoreDataClient'], - legacyRiskEngineDisabled: true, - riskEngineConfigurationCreated: false, - riskEngineEnabled: false, - riskEngineResourcesInstalled: false, - }); + it('should catch error for resource init', async () => { + disableLegacyRiskEngineMock.mockImplementationOnce(() => { + throw new Error('Error disableLegacyRiskEngineMock'); }); - it('should catch error for initSavedObjects and stop', async () => { - jest.spyOn(savedObjectConfig, 'initSavedObjects').mockImplementationOnce(() => { - throw new Error('Error initSavedObjects'); - }); - - const initResult = await riskEngineDataClient.init({ - namespace: 'default', - taskManager: mockTaskManagerStart, - riskScoreDataClient: riskScoreDataClientMock.create(), - }); - - expect(initResult).toEqual({ - errors: ['Error initSavedObjects'], - legacyRiskEngineDisabled: true, - riskEngineConfigurationCreated: false, - riskEngineEnabled: false, - riskEngineResourcesInstalled: true, - }); + const initResult = await riskEngineDataClient.init({ + namespace: 'default', + taskManager: mockTaskManagerStart, + riskScoreDataClient: riskScoreDataClientMock.create(), }); - it('should catch error for enableRiskEngineMock and stop', async () => { - enableRiskEngineMock.mockImplementationOnce(() => { - throw new Error('Error enableRiskEngineMock'); - }); - - const initResult = await riskEngineDataClient.init({ - namespace: 'default', - taskManager: mockTaskManagerStart, - riskScoreDataClient: riskScoreDataClientMock.create(), - }); - - expect(initResult).toEqual({ - errors: ['Error enableRiskEngineMock'], - legacyRiskEngineDisabled: true, - riskEngineConfigurationCreated: true, - riskEngineEnabled: false, - riskEngineResourcesInstalled: true, - }); + expect(initResult).toEqual({ + errors: ['Error disableLegacyRiskEngineMock'], + legacyRiskEngineDisabled: false, + riskEngineConfigurationCreated: true, + riskEngineEnabled: true, + riskEngineResourcesInstalled: true, }); }); - describe('tearDownRiskEngine', () => { - const mockTaskManagerStart = taskManagerMock.createStart(); + it('should catch error for initializeResources and stop', async () => { + const riskScoreDataClient = riskScoreDataClientMock.create(); + riskScoreDataClient.init.mockImplementationOnce(() => { + throw new Error('Error riskScoreDataClient'); + }); - it('should delete the risk engine object and task if it exists', async () => { - mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); - const riskScoreDataClient = riskScoreDataClientMock.create(); - await riskEngineDataClient.tearDown({ - taskManager: mockTaskManagerStart, - riskScoreDataClient, - }); + const initResult = await riskEngineDataClient.init({ + namespace: 'default', + taskManager: mockTaskManagerStart, + riskScoreDataClient, + }); - expect(mockSavedObjectClient.delete).toHaveBeenCalledTimes(1); - expect(mockTaskManagerStart.remove).toHaveBeenCalledTimes(1); - expect(riskScoreDataClient.tearDown).toHaveBeenCalledTimes(1); + expect(initResult).toEqual({ + errors: ['Error riskScoreDataClient'], + legacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: false, + riskEngineEnabled: false, + riskEngineResourcesInstalled: false, }); + }); - it('should return errors when exception is thrown ', async () => { - const error = new Error('testError'); - mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); - mockTaskManagerStart.remove.mockRejectedValueOnce(error); - mockSavedObjectClient.delete.mockRejectedValueOnce(error); + it('should catch error for initSavedObjects and stop', async () => { + jest.spyOn(savedObjectConfig, 'initSavedObjects').mockImplementationOnce(() => { + throw new Error('Error initSavedObjects'); + }); + + const initResult = await riskEngineDataClient.init({ + namespace: 'default', + taskManager: mockTaskManagerStart, + riskScoreDataClient: riskScoreDataClientMock.create(), + }); + + expect(initResult).toEqual({ + errors: ['Error initSavedObjects'], + legacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: false, + riskEngineEnabled: false, + riskEngineResourcesInstalled: true, + }); + }); - const errors = await riskEngineDataClient.tearDown({ - taskManager: mockTaskManagerStart, - riskScoreDataClient: riskScoreDataClientMock.create(), - }); + it('should catch error for enableRiskEngineMock and stop', async () => { + enableRiskEngineMock.mockImplementationOnce(() => { + throw new Error('Error enableRiskEngineMock'); + }); - await expect(errors).toEqual([error, error]); + const initResult = await riskEngineDataClient.init({ + namespace: 'default', + taskManager: mockTaskManagerStart, + riskScoreDataClient: riskScoreDataClientMock.create(), }); - it('should return errors from riskScoreDataClient.tearDown ', async () => { - const error = new Error('testError'); - mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); - const riskScoreDataClient = riskScoreDataClientMock.create(); - riskScoreDataClient.tearDown.mockResolvedValueOnce([error]); + expect(initResult).toEqual({ + errors: ['Error enableRiskEngineMock'], + legacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: true, + riskEngineEnabled: false, + riskEngineResourcesInstalled: true, + }); + }); + }); - const errors = await riskEngineDataClient.tearDown({ - taskManager: mockTaskManagerStart, - riskScoreDataClient, - }); + describe('tearDownRiskEngine', () => { + const mockTaskManagerStart = taskManagerMock.createStart(); - await expect(errors).toEqual([error]); + it('should delete the risk engine object and task if it exists', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); + const riskScoreDataClient = riskScoreDataClientMock.create(); + await riskEngineDataClient.tearDown({ + taskManager: mockTaskManagerStart, + riskScoreDataClient, }); + + expect(mockSavedObjectClient.delete).toHaveBeenCalledTimes(1); + expect(mockTaskManagerStart.remove).toHaveBeenCalledTimes(1); + expect(riskScoreDataClient.tearDown).toHaveBeenCalledTimes(1); + }); + + it('should return errors when exception is thrown ', async () => { + const error = new Error('testError'); + mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); + mockTaskManagerStart.remove.mockRejectedValueOnce(error); + mockSavedObjectClient.delete.mockRejectedValueOnce(error); + + const errors = await riskEngineDataClient.tearDown({ + taskManager: mockTaskManagerStart, + riskScoreDataClient: riskScoreDataClientMock.create(), + }); + + expect(errors).toEqual([error, error]); + }); + + it('should return errors from riskScoreDataClient.tearDown ', async () => { + const error = new Error('testError'); + mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); + const riskScoreDataClient = riskScoreDataClientMock.create(); + riskScoreDataClient.tearDown.mockResolvedValueOnce([error]); + + const errors = await riskEngineDataClient.tearDown({ + taskManager: mockTaskManagerStart, + riskScoreDataClient, + }); + + expect(errors).toEqual([error]); }); }); - } + }); }); diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index 59cd5c32cef4a..fa8c18207a692 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -14,6 +14,10 @@ describe('config validation', () => { Object { "allow_reading_invalid_state": true, "claim_strategy": "update_by_query", + "discovery": Object { + "active_nodes_lookback": "30s", + "interval": 10000, + }, "ephemeral_tasks": Object { "enabled": false, "request_capacity": 10, @@ -22,6 +26,7 @@ describe('config validation', () => { "monitor": true, "warn_threshold": 5000, }, + "kibanas_per_partition": 2, "max_attempts": 3, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, @@ -71,6 +76,10 @@ describe('config validation', () => { Object { "allow_reading_invalid_state": true, "claim_strategy": "update_by_query", + "discovery": Object { + "active_nodes_lookback": "30s", + "interval": 10000, + }, "ephemeral_tasks": Object { "enabled": false, "request_capacity": 10, @@ -79,6 +88,7 @@ describe('config validation', () => { "monitor": true, "warn_threshold": 5000, }, + "kibanas_per_partition": 2, "max_attempts": 3, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, @@ -126,6 +136,10 @@ describe('config validation', () => { Object { "allow_reading_invalid_state": true, "claim_strategy": "update_by_query", + "discovery": Object { + "active_nodes_lookback": "30s", + "interval": 10000, + }, "ephemeral_tasks": Object { "enabled": false, "request_capacity": 10, @@ -134,6 +148,7 @@ describe('config validation', () => { "monitor": true, "warn_threshold": 5000, }, + "kibanas_per_partition": 2, "max_attempts": 3, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, @@ -252,4 +267,30 @@ describe('config validation', () => { const result = configSchema.validate({ claim_strategy: CLAIM_STRATEGY_MGET }); expect(result.poll_interval).toEqual(500); }); + + test('discovery active_nodes_lookback must be a valid duration', () => { + const config: Record = { + discovery: { + active_nodes_lookback: 'foo', + }, + }; + expect(() => { + configSchema.validate(config); + }).toThrowErrorMatchingInlineSnapshot( + `"[discovery.active_nodes_lookback]: active node lookback duration must be a valid duration string"` + ); + }); + + test('discovery active_nodes_lookback must be less than 5m', () => { + const config: Record = { + discovery: { + active_nodes_lookback: '301s', + }, + }; + expect(() => { + configSchema.validate(config); + }).toThrowErrorMatchingInlineSnapshot( + `"[discovery.active_nodes_lookback]: active node lookback duration cannot exceed five minutes"` + ); + }); }); diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index a1e210efa671d..db07494ef4f06 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -6,6 +6,7 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { parseIntervalAsMillisecond } from './lib/intervals'; export const MAX_WORKERS_LIMIT = 100; export const DEFAULT_CAPACITY = 10; @@ -32,6 +33,15 @@ export const DEFAULT_WORKER_UTILIZATION_RUNNING_AVERAGE_WINDOW = 5; export const CLAIM_STRATEGY_UPDATE_BY_QUERY = 'update_by_query'; export const CLAIM_STRATEGY_MGET = 'mget'; +export const DEFAULT_DISCOVERY_INTERVAL_MS = 1000 * 10; // 10 seconds +const MIN_DISCOVERY_INTERVAL_MS = 1000; // 1 second +const MAX_DISCOVERY_INTERVAL_MS = 1000 * 60 * 5; // 5 minutes + +export const DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION = '30s'; +const FIVE_MIN_IN_MS = 5 * 60 * 1000; + +export const DEFAULT_KIBANAS_PER_PARTITION = 2; + export const taskExecutionFailureThresholdSchema = schema.object( { error_threshold: schema.number({ @@ -70,6 +80,26 @@ export const configSchema = schema.object( allow_reading_invalid_state: schema.boolean({ defaultValue: true }), /* The number of normal cost tasks that this Kibana instance will run simultaneously */ capacity: schema.maybe(schema.number({ min: MIN_CAPACITY, max: MAX_CAPACITY })), + discovery: schema.object({ + active_nodes_lookback: schema.string({ + defaultValue: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + validate: (duration) => { + try { + const parsedDurationMs = parseIntervalAsMillisecond(duration); + if (parsedDurationMs > FIVE_MIN_IN_MS) { + return 'active node lookback duration cannot exceed five minutes'; + } + } catch (err) { + return 'active node lookback duration must be a valid duration string'; + } + }, + }), + interval: schema.number({ + defaultValue: DEFAULT_DISCOVERY_INTERVAL_MS, + min: MIN_DISCOVERY_INTERVAL_MS, + max: MAX_DISCOVERY_INTERVAL_MS, + }), + }), ephemeral_tasks: schema.object({ enabled: schema.boolean({ defaultValue: false }), /* How many requests can Task Manager buffer before it rejects new requests. */ @@ -81,6 +111,10 @@ export const configSchema = schema.object( }), }), event_loop_delay: eventLoopDelaySchema, + kibanas_per_partition: schema.number({ + defaultValue: DEFAULT_KIBANAS_PER_PARTITION, + min: 1, + }), /* The maximum number of times a task will be attempted before being abandoned as failed */ max_attempts: schema.number({ defaultValue: 3, diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index 2a6f1bf8c33b8..100555e9ead4c 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -45,6 +45,11 @@ describe('EphemeralTaskLifecycle', () => { definitions: new TaskTypeDictionary(taskManagerLogger), executionContext, config: { + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, max_attempts: 9, poll_interval: 6000000, version_conflict_threshold: 80, diff --git a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts index a44584970e7b5..7e626c5853820 100644 --- a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts @@ -43,6 +43,11 @@ describe('managed configuration', () => { clock = sinon.useFakeTimers(); const context = coreMock.createPluginInitializerContext({ + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, capacity: 10, max_attempts: 9, poll_interval: 3000, @@ -160,6 +165,11 @@ describe('managed configuration', () => { clock = sinon.useFakeTimers(); const context = coreMock.createPluginInitializerContext({ + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, capacity: 10, max_attempts: 9, poll_interval: 3000, @@ -280,6 +290,11 @@ describe('managed configuration', () => { clock = sinon.useFakeTimers(); const context = coreMock.createPluginInitializerContext({ + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, capacity: 10, max_attempts: 9, poll_interval: 3000, diff --git a/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.test.ts b/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.test.ts index 2fe4684b10e90..7a0d9f0b11ce5 100644 --- a/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.test.ts +++ b/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.test.ts @@ -5,15 +5,12 @@ * 2.0. */ import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { - KibanaDiscoveryService, - DISCOVERY_INTERVAL, - ACTIVE_NODES_LOOK_BACK, -} from './kibana_discovery_service'; +import { KibanaDiscoveryService } from './kibana_discovery_service'; import { BACKGROUND_TASK_NODE_SO_NAME } from '../saved_objects'; import { SavedObjectsBulkDeleteResponse, SavedObjectsUpdateResponse } from '@kbn/core/server'; import { createFindResponse, createFindSO } from './mock_kibana_discovery_service'; +import { DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, DEFAULT_DISCOVERY_INTERVAL_MS } from '../config'; const currentNode = 'current-node-id'; const now = '2024-08-10T10:00:00.000Z'; @@ -43,6 +40,10 @@ describe('KibanaDiscoveryService', () => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); await kibanaDiscoveryService.start(); @@ -68,6 +69,10 @@ describe('KibanaDiscoveryService', () => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); await kibanaDiscoveryService.start(); await kibanaDiscoveryService.start(); @@ -84,13 +89,21 @@ describe('KibanaDiscoveryService', () => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); await kibanaDiscoveryService.start(); expect(savedObjectsRepository.update).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenNthCalledWith(1, expect.any(Function), DISCOVERY_INTERVAL); + expect(setTimeout).toHaveBeenNthCalledWith( + 1, + expect.any(Function), + DEFAULT_DISCOVERY_INTERVAL_MS + ); jest.runOnlyPendingTimers(); @@ -104,6 +117,10 @@ describe('KibanaDiscoveryService', () => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); await kibanaDiscoveryService.start(); @@ -113,7 +130,11 @@ describe('KibanaDiscoveryService', () => { ); expect(logger.info).not.toHaveBeenCalled(); expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenNthCalledWith(1, expect.any(Function), DISCOVERY_INTERVAL); + expect(setTimeout).toHaveBeenNthCalledWith( + 1, + expect.any(Function), + DEFAULT_DISCOVERY_INTERVAL_MS + ); }); it('reschedules when upsert fails after start', async () => { @@ -125,6 +146,10 @@ describe('KibanaDiscoveryService', () => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); await kibanaDiscoveryService.start(); @@ -133,7 +158,11 @@ describe('KibanaDiscoveryService', () => { expect(logger.info).toHaveBeenCalledWith('Kibana Discovery Service has been started'); expect(kibanaDiscoveryService.isStarted()).toBe(true); expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenNthCalledWith(1, expect.any(Function), DISCOVERY_INTERVAL); + expect(setTimeout).toHaveBeenNthCalledWith( + 1, + expect.any(Function), + DEFAULT_DISCOVERY_INTERVAL_MS + ); savedObjectsRepository.update.mockRejectedValueOnce(new Error('foo')); @@ -141,7 +170,11 @@ describe('KibanaDiscoveryService', () => { expect(savedObjectsRepository.update).toHaveBeenCalledTimes(2); expect(setTimeout).toHaveBeenCalledTimes(2); - expect(setTimeout).toHaveBeenNthCalledWith(2, expect.any(Function), DISCOVERY_INTERVAL); + expect(setTimeout).toHaveBeenNthCalledWith( + 2, + expect.any(Function), + DEFAULT_DISCOVERY_INTERVAL_MS + ); expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenCalledWith( "Kibana Discovery Service couldn't update this node's last_seen timestamp. id: current-node-id, last_seen: 2024-08-10T10:00:10.000Z, error:foo" @@ -158,12 +191,16 @@ describe('KibanaDiscoveryService', () => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); const activeNodes = await kibanaDiscoveryService.getActiveKibanaNodes(); expect(savedObjectsRepository.find).toHaveBeenCalledWith({ - filter: `${BACKGROUND_TASK_NODE_SO_NAME}.attributes.last_seen > now-${ACTIVE_NODES_LOOK_BACK}`, + filter: `${BACKGROUND_TASK_NODE_SO_NAME}.attributes.last_seen > now-${DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION}`, page: 1, perPage: 10000, type: BACKGROUND_TASK_NODE_SO_NAME, @@ -180,6 +217,10 @@ describe('KibanaDiscoveryService', () => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); await kibanaDiscoveryService.deleteCurrentNode(); diff --git a/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.ts b/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.ts index cde499122ab11..c532cb755f7d9 100644 --- a/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.ts +++ b/x-pack/plugins/task_manager/server/kibana_discovery_service/kibana_discovery_service.ts @@ -9,8 +9,10 @@ import type { ISavedObjectsRepository } from '@kbn/core/server'; import { Logger } from '@kbn/core/server'; import { BACKGROUND_TASK_NODE_SO_NAME } from '../saved_objects'; import { BackgroundTaskNode } from '../saved_objects/schemas/background_task_node'; +import { TaskManagerConfig } from '../config'; interface DiscoveryServiceParams { + config: TaskManagerConfig['discovery']; currentNode: string; savedObjectsRepository: ISavedObjectsRepository; logger: Logger; @@ -21,16 +23,17 @@ interface DiscoveryServiceUpsertParams { lastSeen: string; } -export const DISCOVERY_INTERVAL = 1000 * 10; -export const ACTIVE_NODES_LOOK_BACK = '30s'; - export class KibanaDiscoveryService { + private readonly activeNodesLookBack: string; + private readonly discoveryInterval: number; private currentNode: string; private started = false; private savedObjectsRepository: ISavedObjectsRepository; private logger: Logger; - constructor({ currentNode, savedObjectsRepository, logger }: DiscoveryServiceParams) { + constructor({ config, currentNode, savedObjectsRepository, logger }: DiscoveryServiceParams) { + this.activeNodesLookBack = config.active_nodes_lookback; + this.discoveryInterval = config.interval; this.savedObjectsRepository = savedObjectsRepository; this.logger = logger; this.currentNode = currentNode; @@ -60,7 +63,7 @@ export class KibanaDiscoveryService { } catch (e) { if (!this.started) { this.logger.error( - `Kibana Discovery Service couldn't be started and will be retried in ${DISCOVERY_INTERVAL}ms, error:${e.message}` + `Kibana Discovery Service couldn't be started and will be retried in ${this.discoveryInterval}ms, error:${e.message}` ); } else { this.logger.error( @@ -70,7 +73,7 @@ export class KibanaDiscoveryService { } finally { setTimeout( async () => await this.scheduleUpsertCurrentNode(), - DISCOVERY_INTERVAL - (Date.now() - lastSeenDate.getTime()) + this.discoveryInterval - (Date.now() - lastSeenDate.getTime()) ); } } @@ -93,7 +96,7 @@ export class KibanaDiscoveryService { type: BACKGROUND_TASK_NODE_SO_NAME, perPage: 10000, page: 1, - filter: `${BACKGROUND_TASK_NODE_SO_NAME}.attributes.last_seen > now-${ACTIVE_NODES_LOOK_BACK}`, + filter: `${BACKGROUND_TASK_NODE_SO_NAME}.attributes.last_seen > now-${this.activeNodesLookBack}`, }); return activeNodes; diff --git a/x-pack/plugins/task_manager/server/kibana_discovery_service/mock_kibana_discovery_service.ts b/x-pack/plugins/task_manager/server/kibana_discovery_service/mock_kibana_discovery_service.ts index 39b98d29a149e..eb5956c6c5173 100644 --- a/x-pack/plugins/task_manager/server/kibana_discovery_service/mock_kibana_discovery_service.ts +++ b/x-pack/plugins/task_manager/server/kibana_discovery_service/mock_kibana_discovery_service.ts @@ -10,6 +10,7 @@ import { SavedObjectsFindResponse, SavedObjectsFindResult } from '@kbn/core/serv import { BackgroundTaskNode } from '../saved_objects/schemas/background_task_node'; import { BACKGROUND_TASK_NODE_SO_NAME } from '../saved_objects'; import { KibanaDiscoveryService } from './kibana_discovery_service'; +import { DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, DEFAULT_DISCOVERY_INTERVAL_MS } from '../config'; export const createDiscoveryServiceMock = (currentNode: string) => { const savedObjectsRepository = savedObjectsRepositoryMock.create(); @@ -18,6 +19,10 @@ export const createDiscoveryServiceMock = (currentNode: string) => { savedObjectsRepository, logger, currentNode, + config: { + active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION, + interval: DEFAULT_DISCOVERY_INTERVAL_MS, + }, }); for (const method of ['getActiveKibanaNodes'] as Array) { diff --git a/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.test.ts b/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.test.ts index f7bb5413fc0cd..953db750e7c4b 100644 --- a/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.test.ts +++ b/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.test.ts @@ -5,12 +5,17 @@ * 2.0. */ -import { assignPodPartitions, getParitionMap } from './assign_pod_partitions'; +import { DEFAULT_KIBANAS_PER_PARTITION } from '../config'; +import { assignPodPartitions, getPartitionMap } from './assign_pod_partitions'; describe('assignPodPartitions', () => { test('two pods', () => { const allPods = ['foo', 'bar']; const allPartitions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - const map = getParitionMap(allPods, allPartitions); + const map = getPartitionMap({ + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + podNames: allPods, + partitions: allPartitions, + }); expect(map).toMatchInlineSnapshot(` Object { "1": Array [ @@ -60,7 +65,11 @@ describe('assignPodPartitions', () => { test('three pods', () => { const allPods = ['foo', 'bar', 'quz']; const allPartitions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - const map = getParitionMap(allPods, allPartitions); + const map = getPartitionMap({ + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + podNames: allPods, + partitions: allPartitions, + }); expect(map).toMatchInlineSnapshot(` Object { "1": Array [ @@ -105,7 +114,12 @@ describe('assignPodPartitions', () => { ], } `); - const fooPartitions = assignPodPartitions('foo', allPods, allPartitions); + const fooPartitions = assignPodPartitions({ + podName: 'foo', + podNames: allPods, + partitions: allPartitions, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + }); expect(fooPartitions).toMatchInlineSnapshot(` Array [ 1, @@ -117,7 +131,12 @@ describe('assignPodPartitions', () => { 10, ] `); - const barPartitions = assignPodPartitions('bar', allPods, allPartitions); + const barPartitions = assignPodPartitions({ + podName: 'bar', + podNames: allPods, + partitions: allPartitions, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + }); expect(barPartitions).toMatchInlineSnapshot(` Array [ 1, @@ -129,7 +148,12 @@ describe('assignPodPartitions', () => { 10, ] `); - const quzPartitions = assignPodPartitions('quz', allPods, allPartitions); + const quzPartitions = assignPodPartitions({ + podName: 'quz', + podNames: allPods, + partitions: allPartitions, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + }); expect(quzPartitions).toMatchInlineSnapshot(` Array [ 2, diff --git a/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.ts b/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.ts index c2d5554b01f1b..8639edd048336 100644 --- a/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.ts +++ b/x-pack/plugins/task_manager/server/lib/assign_pod_partitions.ts @@ -5,26 +5,42 @@ * 2.0. */ -const KIBANAS_PER_PARTITION = 2; +interface GetPartitionMapOpts { + kibanasPerPartition: number; + partitions: number[]; + podNames: string[]; +} -export function getParitionMap(podNames: string[], partitions: number[]): Record { +export function getPartitionMap({ + kibanasPerPartition, + podNames, + partitions, +}: GetPartitionMapOpts): Record { const map: Record = {}; let counter = 0; - for (const parition of partitions) { - map[parition] = []; - for (let i = 0; i < KIBANAS_PER_PARTITION; i++) { - map[parition].push(podNames.sort()[counter++ % podNames.length]); + for (const partition of partitions) { + map[partition] = []; + for (let i = 0; i < kibanasPerPartition; i++) { + map[partition].push(podNames.sort()[counter++ % podNames.length]); } } return map; } -export function assignPodPartitions( - podName: string, - podNames: string[], - partitions: number[] -): number[] { - const map = getParitionMap(podNames, partitions); +interface AssignPodPartitionsOpts { + kibanasPerPartition: number; + podName: string; + podNames: string[]; + partitions: number[]; +} + +export function assignPodPartitions({ + kibanasPerPartition, + podName, + podNames, + partitions, +}: AssignPodPartitionsOpts): number[] { + const map = getPartitionMap({ kibanasPerPartition, podNames, partitions }); const podPartitions: number[] = []; for (const partition of Object.keys(map)) { if (map[Number(partition)].indexOf(podName) !== -1) { diff --git a/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts b/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts index 49c68459982ba..3943e94bdb8b3 100644 --- a/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts +++ b/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts @@ -15,6 +15,11 @@ Date.now = jest.fn().mockReturnValue(new Date(now)); const logger = loggingSystemMock.create().get(); const config = { + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, enabled: true, index: 'foo', max_attempts: 9, diff --git a/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts b/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts index 8bab96f85dee5..891a55a4d2e52 100644 --- a/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts +++ b/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_KIBANAS_PER_PARTITION } from '../config'; import { createDiscoveryServiceMock, createFindSO, @@ -16,7 +17,11 @@ const POD_NAME = 'test-pod'; describe('getAllPartitions()', () => { const discoveryServiceMock = createDiscoveryServiceMock(POD_NAME); test('correctly sets allPartitions in constructor', () => { - const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); + const taskPartitioner = new TaskPartitioner({ + podName: POD_NAME, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + kibanaDiscoveryService: discoveryServiceMock, + }); expect(taskPartitioner.getAllPartitions()).toEqual([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, @@ -39,7 +44,11 @@ describe('getPodName()', () => { const discoveryServiceMock = createDiscoveryServiceMock(POD_NAME); test('correctly sets podName in constructor', () => { - const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); + const taskPartitioner = new TaskPartitioner({ + podName: POD_NAME, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + kibanaDiscoveryService: discoveryServiceMock, + }); expect(taskPartitioner.getPodName()).toEqual('test-pod'); }); }); @@ -74,12 +83,20 @@ describe('getPartitions()', () => { }); test('correctly gets the partitons for this pod', async () => { - const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); + const taskPartitioner = new TaskPartitioner({ + podName: POD_NAME, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + kibanaDiscoveryService: discoveryServiceMock, + }); expect(await taskPartitioner.getPartitions()).toEqual(expectedPartitions); }); test('correctly caches the partitions on 10 second interval', async () => { - const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); + const taskPartitioner = new TaskPartitioner({ + podName: POD_NAME, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + kibanaDiscoveryService: discoveryServiceMock, + }); const shorterInterval = CACHE_INTERVAL / 2; await taskPartitioner.getPartitions(); @@ -94,8 +111,11 @@ describe('getPartitions()', () => { }); test('correctly catches the error from the discovery service and returns the cached value', async () => { - const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); - + const taskPartitioner = new TaskPartitioner({ + podName: POD_NAME, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + kibanaDiscoveryService: discoveryServiceMock, + }); await taskPartitioner.getPartitions(); expect(taskPartitioner.getPodPartitions()).toEqual(expectedPartitions); diff --git a/x-pack/plugins/task_manager/server/lib/task_partitioner.ts b/x-pack/plugins/task_manager/server/lib/task_partitioner.ts index f0388ce19d966..ff1633911f62d 100644 --- a/x-pack/plugins/task_manager/server/lib/task_partitioner.ts +++ b/x-pack/plugins/task_manager/server/lib/task_partitioner.ts @@ -19,17 +19,24 @@ function range(start: number, end: number) { export const MAX_PARTITIONS = 256; export const CACHE_INTERVAL = 10000; +export interface TaskPartitionerConstructorOpts { + kibanaDiscoveryService: KibanaDiscoveryService; + kibanasPerPartition: number; + podName: string; +} export class TaskPartitioner { private readonly allPartitions: number[]; private readonly podName: string; + private readonly kibanasPerPartition: number; private kibanaDiscoveryService: KibanaDiscoveryService; private podPartitions: number[]; private podPartitionsLastUpdated: number; - constructor(podName: string, kibanaDiscoveryService: KibanaDiscoveryService) { + constructor(opts: TaskPartitionerConstructorOpts) { this.allPartitions = range(0, MAX_PARTITIONS); - this.podName = podName; - this.kibanaDiscoveryService = kibanaDiscoveryService; + this.podName = opts.podName; + this.kibanasPerPartition = opts.kibanasPerPartition; + this.kibanaDiscoveryService = opts.kibanaDiscoveryService; this.podPartitions = []; this.podPartitionsLastUpdated = Date.now() - CACHE_INTERVAL; } @@ -54,7 +61,12 @@ export class TaskPartitioner { if (now - lastUpdated >= CACHE_INTERVAL) { try { const allPodNames = await this.getAllPodNames(); - this.podPartitions = assignPodPartitions(this.podName, allPodNames, this.allPartitions); + this.podPartitions = assignPodPartitions({ + kibanasPerPartition: this.kibanasPerPartition, + podName: this.podName, + podNames: allPodNames, + partitions: this.allPartitions, + }); this.podPartitionsLastUpdated = now; } catch (error) { // return the cached value diff --git a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts index b1cf9a90b6cb6..b39e1b3168731 100644 --- a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts @@ -35,6 +35,11 @@ import { TaskOverdueMetric, TaskOverdueMetricsAggregator } from './task_overdue_ const logger = loggingSystemMock.createLogger(); const mockMetricsAggregator = metricsAggregatorMock.create(); const config: TaskManagerConfig = { + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, allow_reading_invalid_state: false, ephemeral_tasks: { enabled: true, diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts index 0b5387b66dece..a169c9dad8fe5 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts @@ -13,6 +13,11 @@ import { TaskManagerConfig } from '../config'; describe('Configuration Statistics Aggregator', () => { test('merges the static config with the merged configs', async () => { const configuration: TaskManagerConfig = { + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, max_attempts: 9, poll_interval: 6000000, allow_reading_invalid_state: false, diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts index acad060106112..96269f58158df 100644 --- a/x-pack/plugins/task_manager/server/plugin.test.ts +++ b/x-pack/plugins/task_manager/server/plugin.test.ts @@ -49,6 +49,11 @@ const pluginInitializerContextParams = { version_conflict_threshold: 80, request_capacity: 1000, allow_reading_invalid_state: false, + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, monitored_aggregated_stats_refresh_rate: 5000, monitored_stats_health_verbose_log: { enabled: false, diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 44e750b17d9d6..a746d8a9c3580 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -259,6 +259,7 @@ export class TaskManagerPlugin savedObjectsRepository, logger: this.logger, currentNode: this.taskManagerId!, + config: this.config.discovery, }); if (this.shouldRunBackgroundTasks) { @@ -315,7 +316,12 @@ export class TaskManagerPlugin excludedTypes: new Set(this.config.unsafe.exclude_task_types), }); - const taskPartitioner = new TaskPartitioner(this.taskManagerId!, this.kibanaDiscoveryService); + const taskPartitioner = new TaskPartitioner({ + podName: this.taskManagerId!, + kibanaDiscoveryService: this.kibanaDiscoveryService, + kibanasPerPartition: this.config.kibanas_per_partition, + }); + this.taskPollingLifecycle = new TaskPollingLifecycle({ config: this.config!, definitions: this.definitions, diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts index 77e29749c97a2..db1e5277ce37f 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts @@ -21,7 +21,7 @@ import { FillPoolResult } from './lib/fill_pool'; import { ElasticsearchResponseError } from './lib/identify_es_error'; import { executionContextServiceMock } from '@kbn/core/server/mocks'; import { TaskCost } from './task'; -import { CLAIM_STRATEGY_MGET } from './config'; +import { CLAIM_STRATEGY_MGET, DEFAULT_KIBANAS_PER_PARTITION } from './config'; import { TaskPartitioner } from './lib/task_partitioner'; import { KibanaDiscoveryService } from './kibana_discovery_service'; @@ -45,6 +45,11 @@ describe('TaskPollingLifecycle', () => { const mockTaskStore = taskStoreMock.create({}); const taskManagerOpts = { config: { + discovery: { + active_nodes_lookback: '30s', + interval: 10000, + }, + kibanas_per_partition: 2, enabled: true, index: 'foo', max_attempts: 9, @@ -95,7 +100,11 @@ describe('TaskPollingLifecycle', () => { capacityConfiguration$: of(20), pollIntervalConfiguration$: of(100), executionContext, - taskPartitioner: new TaskPartitioner('test', {} as KibanaDiscoveryService), + taskPartitioner: new TaskPartitioner({ + podName: 'test', + kibanaDiscoveryService: {} as KibanaDiscoveryService, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, + }), }; beforeEach(() => { diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index 4528575d29ad0..903f6601c7b9c 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -12,6 +12,7 @@ import { taskStoreMock } from '../task_store.mock'; import apm from 'elastic-apm-node'; import { TaskPartitioner } from '../lib/task_partitioner'; import { KibanaDiscoveryService } from '../kibana_discovery_service'; +import { DEFAULT_KIBANAS_PER_PARTITION } from '../config'; jest.mock('../constants', () => ({ CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: [ @@ -25,7 +26,11 @@ jest.mock('../constants', () => ({ })); const taskManagerLogger = mockLogger(); -const taskPartitioner = new TaskPartitioner('test', {} as KibanaDiscoveryService); +const taskPartitioner = new TaskPartitioner({ + podName: 'test', + kibanaDiscoveryService: {} as KibanaDiscoveryService, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, +}); beforeEach(() => jest.clearAllMocks()); diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts index 96109d30d1cae..4e47581ccbdd5 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts @@ -10,7 +10,7 @@ import { v4 as uuidv4 } from 'uuid'; import { filter, take, toArray } from 'rxjs'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; -import { CLAIM_STRATEGY_MGET } from '../config'; +import { CLAIM_STRATEGY_MGET, DEFAULT_KIBANAS_PER_PARTITION } from '../config'; import { TaskStatus, @@ -101,7 +101,11 @@ discoveryServiceMock.getActiveKibanaNodes.mockResolvedValue([ createFindSO('test-pod-2', lastSeen), createFindSO('test-pod-3', lastSeen), ]); -const taskPartitioner = new TaskPartitioner('test', discoveryServiceMock); +const taskPartitioner = new TaskPartitioner({ + podName: 'test', + kibanaDiscoveryService: discoveryServiceMock, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, +}); // needs more tests in the similar to the `strategy_default.test.ts` test suite describe('TaskClaiming', () => { diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts index 38676ee1626e7..7744543ea9577 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_update_by_query.test.ts @@ -30,6 +30,7 @@ import { ClaimOwnershipResult } from '.'; import { FillPoolResult } from '../lib/fill_pool'; import { TaskPartitioner } from '../lib/task_partitioner'; import { KibanaDiscoveryService } from '../kibana_discovery_service'; +import { DEFAULT_KIBANAS_PER_PARTITION } from '../config'; jest.mock('../constants', () => ({ CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: [ @@ -43,7 +44,11 @@ jest.mock('../constants', () => ({ })); const taskManagerLogger = mockLogger(); -const taskPartitioner = new TaskPartitioner('test', {} as KibanaDiscoveryService); +const taskPartitioner = new TaskPartitioner({ + podName: 'test', + kibanaDiscoveryService: {} as KibanaDiscoveryService, + kibanasPerPartition: DEFAULT_KIBANAS_PER_PARTITION, +}); beforeEach(() => jest.clearAllMocks()); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 4ed818823f132..9d86049531ed7 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -10931,7 +10931,7 @@ "xpack.apm.onboarding.shared_clients.configure.commands.serverUrlHint": "Définir l'URL personnalisée du serveur APM (par défaut : {defaultApmServerUrl}). L'URL doit être complète et inclure le protocole (http ou https) et le port.", "xpack.apm.onboarding.shared_clients.configure.commands.serviceEnvironmentHint": "Le nom de l'environnement dans lequel ce service est déployé, par exemple \"production\" ou \"test\". Les environnements vous permettent de facilement filtrer les données à un niveau global dans l'interface utilisateur APM. Il est important de garantir la cohérence des noms d'environnements entre les différents agents.", "xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "Le nom de service est le filtre principal dans l'interface utilisateur APM et est utilisé pour regrouper les erreurs et suivre les données ensemble. Caractères autorisés : a-z, A-Z, 0-9, -, _ et espace.", - "xpack.apm.onboarding.specProvider.longDescription": "Le monitoring des performances applicatives (APM) collecte les indicateurs et les erreurs de performance approfondies depuis votre application. Cela vous permet de monitorer les performances de milliers d'applications en temps réel. [Learn more]({learnMoreLink}).", + "xpack.apm.onboarding.specProvider.longDescription": "Le monitoring des performances applicatives (APM) collecte les indicateurs et les erreurs de performance approfondies depuis votre application. Cela vous permet de monitorer les performances de milliers d'applications en temps réel. {learnMoreLink}.", "xpack.apm.pages.alertDetails.alertSummary.actualValue": "Valeur réelle", "xpack.apm.pages.alertDetails.alertSummary.expectedValue": "Valeur attendue", "xpack.apm.percentOfParent": "({value} de {parentType, select, transaction { transaction } trace {trace} other {parentType inconnu} })", @@ -14452,7 +14452,6 @@ "xpack.csp.gcpIntegration.projectidFieldLabel": "ID de projet", "xpack.csp.gcpIntegration.setupFormatOptions.googleCloudShell": "Google Cloud Shell", "xpack.csp.gcpIntegration.setupFormatOptions.manual": "Manuel", - "xpack.csp.gcpIntegration.setupInfoContent": "L'intégration nécessitera des droits d'accès supérieurs pour l'exécution de certaines règles CIS Benchmarks. Sélectionnez votre méthode préférée pour la fourniture d'informations d'identification Google Cloud que cette intégration utilisera. Vous pouvez suivre ces instructions détaillées pour générer les informations d'identification nécessaires.", "xpack.csp.gcpIntegration.setupInfoContentTitle": "Configurer l'accès", "xpack.csp.grouping.loadingGroupPanelTitle": "Chargement", "xpack.csp.grouping.nullGroupTooltip": "Le champ {groupingTitle} sélectionné, {field}, a une valeur manquante de {unit} pour ce groupe.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index de0a83a8442c7..260c61347241d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10920,7 +10920,7 @@ "xpack.apm.onboarding.shared_clients.configure.commands.serverUrlHint": "カスタム APM Server URL(デフォルト:{defaultApmServerUrl})を設定します。URLはプロトコル(httpまたはhttps)とポートを含む完全修飾URLでなければなりません。", "xpack.apm.onboarding.shared_clients.configure.commands.serviceEnvironmentHint": "このサービスがデプロイされている環境の名前(例:「本番」、「ステージング」)。環境では、APM UIでグローバルレベルで簡単にデータをフィルタリングできます。すべてのエージェントで環境の命名方法を統一することが重要です。", "xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "このサービス名はAPM UIの主フィルターであり、エラーとトレースデータをグループ化するために使用されます。使用できる文字はA-Z、0-9、-、_、スペースです。", - "xpack.apm.onboarding.specProvider.longDescription": "アプリケーションパフォーマンスモニタリング(APM)は、アプリケーション内から詳細なパフォーマンスメトリックやエラーを収集します。何千ものアプリケーションのパフォーマンスをリアルタイムで監視できます。[詳細]({learnMoreLink})。", + "xpack.apm.onboarding.specProvider.longDescription": "アプリケーションパフォーマンスモニタリング(APM)は、アプリケーション内から詳細なパフォーマンスメトリックやエラーを収集します。何千ものアプリケーションのパフォーマンスをリアルタイムで監視できます。{learnMoreLink}。", "xpack.apm.pages.alertDetails.alertSummary.actualValue": "実際の値", "xpack.apm.pages.alertDetails.alertSummary.expectedValue": "想定された値", "xpack.apm.percentOfParent": "({value} of {parentType, select, transaction { トランザクション } trace {トレース} other {不明なparentType} })", @@ -14441,7 +14441,6 @@ "xpack.csp.gcpIntegration.projectidFieldLabel": "プロジェクト ID", "xpack.csp.gcpIntegration.setupFormatOptions.googleCloudShell": "Google Cloud Shell", "xpack.csp.gcpIntegration.setupFormatOptions.manual": "手動", - "xpack.csp.gcpIntegration.setupInfoContent": "この統合では、一部のCISベンチマークルールを実行するために昇格されたアクセス権が必要です。この統合で使用するGCP資格情報を提供するための任意の方法を選択します。これらの段階的な手順に従い、必要な資格情報を作成できます。", "xpack.csp.gcpIntegration.setupInfoContentTitle": "アクセスの設定", "xpack.csp.grouping.loadingGroupPanelTitle": "読み込み中", "xpack.csp.grouping.nullGroupTooltip.groupingTitle": "グループ分けの条件", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b378296cbf12b..690519b75013f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10939,7 +10939,7 @@ "xpack.apm.onboarding.shared_clients.configure.commands.serverUrlHint": "设置定制 APM Server URL(默认值:{defaultApmServerUrl})。此 URL 必须为完全限定 URL,包括协议(http 或 https)和端口。", "xpack.apm.onboarding.shared_clients.configure.commands.serviceEnvironmentHint": "在其中部署此服务的环境的名称,如“生产”或“暂存”。在 APM UI 中,您可以通过环境在全局级别轻松筛选数据。跨代理命名环境时,保持一致至关重要。", "xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "服务名称是 APM UI 中的初级筛选,用于分组错误并跟踪数据。允许使用的字符包括 a-z、A-Z、0-9、-、_ 和空格。", - "xpack.apm.onboarding.specProvider.longDescription": "应用程序性能监测 (APM) 从您的应用程序内收集深入全面的性能指标和错误。其允许您实时监测数以千计的应用程序的性能。[了解详情]({learnMoreLink})。", + "xpack.apm.onboarding.specProvider.longDescription": "应用程序性能监测 (APM) 从您的应用程序内收集深入全面的性能指标和错误。其允许您实时监测数以千计的应用程序的性能。{learnMoreLink}。", "xpack.apm.pages.alertDetails.alertSummary.actualValue": "实际值", "xpack.apm.pages.alertDetails.alertSummary.expectedValue": "预期值", "xpack.apm.percentOfParent": "({parentType, select, transaction {事务} trace {追溯} other {parentType 未知}}的 {value})", @@ -14463,7 +14463,6 @@ "xpack.csp.gcpIntegration.projectidFieldLabel": "项目 ID", "xpack.csp.gcpIntegration.setupFormatOptions.googleCloudShell": "Google Cloud Shell", "xpack.csp.gcpIntegration.setupFormatOptions.manual": "手动", - "xpack.csp.gcpIntegration.setupInfoContent": "该集成需要提升访问权限才能运行某些 CIS 基准规则。选择提供此集成将使用的 GCP 凭据的首选方法。您可以按照这些分步说明生成所需凭据。", "xpack.csp.gcpIntegration.setupInfoContentTitle": "设置访问权限", "xpack.csp.grouping.loadingGroupPanelTitle": "正在加载", "xpack.csp.grouping.nullGroupTooltip": "选定 {groupingTitle} 字段 {field} 缺少此 {unit} 组的值。", diff --git a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts index 6a30645c60b06..415e3e165cff3 100644 --- a/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts +++ b/x-pack/test/rule_registry/spaces_only/tests/trial/lifecycle_executor.ts @@ -13,7 +13,7 @@ // I fixed this as a drive-by, but opened an issue to do something later, // if needed: https://github.com/elastic/kibana/issues/144557 -import { type Subject, ReplaySubject } from 'rxjs'; +import { type Subject, ReplaySubject, of } from 'rxjs'; import type { ElasticsearchClient, Logger, LogMeta } from '@kbn/core/server'; import sinon from 'sinon'; import expect from '@kbn/expect'; @@ -70,6 +70,7 @@ export default function createLifecycleExecutorApiTest({ getService }: FtrProvid describe('createLifecycleExecutor', () => { let ruleDataClient: IRuleDataClient; let pluginStop$: Subject; + const elasticsearchAndSOAvailability$ = of(true); before(async () => { // First we need to setup the data service. This happens within the @@ -89,6 +90,7 @@ export default function createLifecycleExecutorApiTest({ getService }: FtrProvid }, pluginStop$, dataStreamAdapter, + elasticsearchAndSOAvailability$, }); // This initializes the service. This happens immediately after the creation diff --git a/yarn.lock b/yarn.lock index 9ed40dbc3d931..2dcc9ac31d36f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -163,7 +163,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.24.7", "@babel/core@^7.7.5": +"@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.24.7", "@babel/core@^7.7.5": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== @@ -435,7 +435,7 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.8", "@babel/parser@^7.23.0", "@babel/parser@^7.24.7": +"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.8", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== @@ -2961,114 +2961,114 @@ dependencies: "@istanbuljs/schema" "^0.1.2" -"@istanbuljs/schema@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" - integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.1.tgz#b48ba7b9c34b51483e6d590f46e5837f1ab5f639" - integrity sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q== +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.6.1" - jest-util "^29.6.1" + jest-message-util "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" -"@jest/core@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.1.tgz#fac0d9ddf320490c93356ba201451825231e95f6" - integrity sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ== - dependencies: - "@jest/console" "^29.6.1" - "@jest/reporters" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.5.0" - jest-config "^29.6.1" - jest-haste-map "^29.6.1" - jest-message-util "^29.6.1" - jest-regex-util "^29.4.3" - jest-resolve "^29.6.1" - jest-resolve-dependencies "^29.6.1" - jest-runner "^29.6.1" - jest-runtime "^29.6.1" - jest-snapshot "^29.6.1" - jest-util "^29.6.1" - jest-validate "^29.6.1" - jest-watcher "^29.6.1" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" micromatch "^4.0.4" - pretty-format "^29.6.1" + pretty-format "^29.7.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.1.tgz#ee358fff2f68168394b4a50f18c68278a21fe82f" - integrity sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A== +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== dependencies: - "@jest/fake-timers" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.6.1" + jest-mock "^29.7.0" -"@jest/expect-utils@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.1.tgz#ab83b27a15cdd203fe5f68230ea22767d5c3acc5" - integrity sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw== +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== dependencies: - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" -"@jest/expect@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.1.tgz#fef18265188f6a97601f1ea0a2912d81a85b4657" - integrity sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg== +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== dependencies: - expect "^29.6.1" - jest-snapshot "^29.6.1" + expect "^29.7.0" + jest-snapshot "^29.7.0" -"@jest/fake-timers@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.1.tgz#c773efddbc61e1d2efcccac008139f621de57c69" - integrity sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg== +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.6.1" - jest-mock "^29.6.1" - jest-util "^29.6.1" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" -"@jest/globals@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.1.tgz#c8a8923e05efd757308082cc22893d82b8aa138f" - integrity sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A== +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== dependencies: - "@jest/environment" "^29.6.1" - "@jest/expect" "^29.6.1" - "@jest/types" "^29.6.1" - jest-mock "^29.6.1" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" -"@jest/reporters@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.1.tgz#3325a89c9ead3cf97ad93df3a427549d16179863" - integrity sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA== +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" @@ -3077,52 +3077,52 @@ glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" + istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.6.1" - jest-util "^29.6.1" - jest-worker "^29.6.1" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.6.0", "@jest/schemas@^29.6.3": +"@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" - integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== dependencies: "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.1.tgz#850e565a3f58ee8ca6ec424db00cb0f2d83c36ba" - integrity sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw== +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== dependencies: - "@jest/console" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz#e3e582ee074dd24ea9687d7d1aaf05ee3a9b068e" - integrity sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg== +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== dependencies: - "@jest/test-result" "^29.6.1" + "@jest/test-result" "^29.7.0" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" + jest-haste-map "^29.7.0" slash "^3.0.0" "@jest/transform@^26.6.2": @@ -3146,22 +3146,22 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/transform@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.1.tgz#acb5606019a197cb99beda3c05404b851f441c92" - integrity sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg== +"@jest/transform@^29.6.1", "@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" - jest-regex-util "^29.4.3" - jest-util "^29.6.1" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -3178,12 +3178,12 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jest/types@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" - integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^29.6.0" + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -5215,6 +5215,10 @@ version "0.0.0" uid "" +"@kbn/hardening-plugin@link:test/plugin_functional/plugins/hardening": + version "0.0.0" + uid "" + "@kbn/health-gateway-server@link:packages/kbn-health-gateway-server": version "0.0.0" uid "" @@ -10976,7 +10980,7 @@ dependencies: "@types/node" "*" -"@types/prettier@^2.0.0", "@types/prettier@^2.1.5": +"@types/prettier@^2.0.0": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== @@ -12853,15 +12857,15 @@ b4a@^1.6.4: resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw== -babel-jest@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.1.tgz#a7141ad1ed5ec50238f3cd36127636823111233a" - integrity sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A== +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== dependencies: - "@jest/transform" "^29.6.1" + "@jest/transform" "^29.7.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.5.0" + babel-preset-jest "^29.6.3" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -12928,10 +12932,10 @@ babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" - integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -13057,12 +13061,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" - integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== dependencies: - babel-plugin-jest-hoist "^29.5.0" + babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" babel-runtime@6.x, babel-runtime@^6.26.0: @@ -14869,6 +14873,19 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -15705,6 +15722,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + deep-equal@^1.0.0, deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -17586,17 +17608,16 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" -expect@^29.0.0, expect@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.1.tgz#64dd1c8f75e2c0b209418f2b8d36a07921adfdf1" - integrity sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g== +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== dependencies: - "@jest/expect-utils" "^29.6.1" - "@types/node" "*" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.6.1" - jest-message-util "^29.6.1" - jest-util "^29.6.1" + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" expiry-js@0.1.7: version "0.1.7" @@ -20750,7 +20771,7 @@ istanbul-lib-instrument@^4.0.0: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: +istanbul-lib-instrument@^5.0.4: version "5.1.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== @@ -20761,6 +20782,17 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + istanbul-lib-processinfo@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz#e1426514662244b2f25df728e8fd1ba35fe53b9c" @@ -20831,83 +20863,83 @@ jest-canvas-mock@^2.5.2: cssfontparser "^1.2.1" moo-color "^1.0.2" -jest-changed-files@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" - integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== dependencies: execa "^5.0.0" + jest-util "^29.7.0" p-limit "^3.1.0" -jest-circus@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.1.tgz#861dab37e71a89907d1c0fabc54a0019738ed824" - integrity sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ== +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== dependencies: - "@jest/environment" "^29.6.1" - "@jest/expect" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^29.6.1" - jest-matcher-utils "^29.6.1" - jest-message-util "^29.6.1" - jest-runtime "^29.6.1" - jest-snapshot "^29.6.1" - jest-util "^29.6.1" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" p-limit "^3.1.0" - pretty-format "^29.6.1" + pretty-format "^29.7.0" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.1.tgz#99d9afa7449538221c71f358f0fdd3e9c6e89f72" - integrity sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing== +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== dependencies: - "@jest/core" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" chalk "^4.0.0" + create-jest "^29.7.0" exit "^0.1.2" - graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.6.1" - jest-util "^29.6.1" - jest-validate "^29.6.1" - prompts "^2.0.1" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" yargs "^17.3.1" -jest-config@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.1.tgz#d785344509065d53a238224c6cdc0ed8e2f2f0dd" - integrity sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ== +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.6.1" - "@jest/types" "^29.6.1" - babel-jest "^29.6.1" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.6.1" - jest-environment-node "^29.6.1" - jest-get-type "^29.4.3" - jest-regex-util "^29.4.3" - jest-resolve "^29.6.1" - jest-runner "^29.6.1" - jest-util "^29.6.1" - jest-validate "^29.6.1" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.6.1" + pretty-format "^29.7.0" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -20921,7 +20953,7 @@ jest-diff@^26.0.0, jest-diff@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-diff@^29.0.3, jest-diff@^29.6.1: +jest-diff@^29.0.3, jest-diff@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== @@ -20931,56 +20963,56 @@ jest-diff@^29.0.3, jest-diff@^29.6.1: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-docblock@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" - integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== dependencies: detect-newline "^3.0.0" -jest-each@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.1.tgz#975058e5b8f55c6780beab8b6ab214921815c89c" - integrity sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ== +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" chalk "^4.0.0" - jest-get-type "^29.4.3" - jest-util "^29.6.1" - pretty-format "^29.6.1" - -jest-environment-jsdom@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.6.1.tgz#480bce658aa31589309c82ca510351fd7c683bbb" - integrity sha512-PoY+yLaHzVRhVEjcVKSfJ7wXmJW4UqPYNhR05h7u/TK0ouf6DmRNZFBL/Z00zgQMyWGMBXn69/FmOvhEJu8cIw== - dependencies: - "@jest/environment" "^29.6.1" - "@jest/fake-timers" "^29.6.1" - "@jest/types" "^29.6.1" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-jsdom@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^29.6.1" - jest-util "^29.6.1" + jest-mock "^29.7.0" + jest-util "^29.7.0" jsdom "^20.0.0" -jest-environment-node@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.1.tgz#08a122dece39e58bc388da815a2166c58b4abec6" - integrity sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ== +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== dependencies: - "@jest/environment" "^29.6.1" - "@jest/fake-timers" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.6.1" - jest-util "^29.6.1" + jest-mock "^29.7.0" + jest-util "^29.7.0" jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-get-type@^29.4.3, jest-get-type@^29.6.3: +jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== @@ -21006,32 +21038,32 @@ jest-haste-map@^26.6.2: optionalDependencies: fsevents "^2.1.2" -jest-haste-map@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.1.tgz#62655c7a1c1b349a3206441330fb2dbdb4b63803" - integrity sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig== +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.4.3" - jest-util "^29.6.1" - jest-worker "^29.6.1" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz#66a902c81318e66e694df7d096a95466cb962f8e" - integrity sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ== +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== dependencies: - jest-get-type "^29.4.3" - pretty-format "^29.6.1" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" jest-matcher-utils@^26.6.2: version "26.6.2" @@ -21043,15 +21075,15 @@ jest-matcher-utils@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-matcher-utils@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz#6c60075d84655d6300c5d5128f46531848160b53" - integrity sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA== +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== dependencies: chalk "^4.0.0" - jest-diff "^29.6.1" - jest-get-type "^29.4.3" - pretty-format "^29.6.1" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" jest-message-util@^26.6.2: version "26.6.2" @@ -21068,29 +21100,29 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" -jest-message-util@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.1.tgz#d0b21d87f117e1b9e165e24f245befd2ff34ff8d" - integrity sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ== +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.6.1" + pretty-format "^29.7.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.1.tgz#049ee26aea8cbf54c764af649070910607316517" - integrity sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw== +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-util "^29.6.1" + jest-util "^29.7.0" jest-pnp-resolver@^1.2.2: version "1.2.2" @@ -21102,18 +21134,18 @@ jest-regex-util@^26.0.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-regex-util@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" - integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== -jest-resolve-dependencies@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz#b85b06670f987a62515bbf625d54a499e3d708f5" - integrity sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw== +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== dependencies: - jest-regex-util "^29.4.3" - jest-snapshot "^29.6.1" + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" jest-resolve@^26.6.2: version "26.6.2" @@ -21129,73 +21161,73 @@ jest-resolve@^26.6.2: resolve "^1.18.1" slash "^3.0.0" -jest-resolve@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.1.tgz#4c3324b993a85e300add2f8609f51b80ddea39ee" - integrity sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg== +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" + jest-haste-map "^29.7.0" jest-pnp-resolver "^1.2.2" - jest-util "^29.6.1" - jest-validate "^29.6.1" + jest-util "^29.7.0" + jest-validate "^29.7.0" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.1.tgz#54557087e7972d345540d622ab5bfc3d8f34688c" - integrity sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ== - dependencies: - "@jest/console" "^29.6.1" - "@jest/environment" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.4.3" - jest-environment-node "^29.6.1" - jest-haste-map "^29.6.1" - jest-leak-detector "^29.6.1" - jest-message-util "^29.6.1" - jest-resolve "^29.6.1" - jest-runtime "^29.6.1" - jest-util "^29.6.1" - jest-watcher "^29.6.1" - jest-worker "^29.6.1" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.1.tgz#8a0fc9274ef277f3d70ba19d238e64334958a0dc" - integrity sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ== - dependencies: - "@jest/environment" "^29.6.1" - "@jest/fake-timers" "^29.6.1" - "@jest/globals" "^29.6.1" - "@jest/source-map" "^29.6.0" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" - jest-message-util "^29.6.1" - jest-mock "^29.6.1" - jest-regex-util "^29.4.3" - jest-resolve "^29.6.1" - jest-snapshot "^29.6.1" - jest-util "^29.6.1" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" strip-bom "^4.0.0" @@ -21229,31 +21261,30 @@ jest-snapshot@^26.3.0: pretty-format "^26.6.2" semver "^7.3.2" -jest-snapshot@^29.0.0, jest-snapshot@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.1.tgz#0d083cb7de716d5d5cdbe80d598ed2fbafac0239" - integrity sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A== +jest-snapshot@^29.0.0, jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" - "@types/prettier" "^2.1.5" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.6.1" + expect "^29.7.0" graceful-fs "^4.2.9" - jest-diff "^29.6.1" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.6.1" - jest-message-util "^29.6.1" - jest-util "^29.6.1" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" natural-compare "^1.4.0" - pretty-format "^29.6.1" + pretty-format "^29.7.0" semver "^7.5.3" jest-specific-snapshot@^4.0.0: @@ -21289,42 +21320,42 @@ jest-util@^26.6.2: is-ci "^2.0.0" micromatch "^4.0.2" -jest-util@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" - integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.1.tgz#765e684af6e2c86dce950aebefbbcd4546d69f7b" - integrity sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA== +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" leven "^3.1.0" - pretty-format "^29.6.1" + pretty-format "^29.7.0" -jest-watcher@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.1.tgz#7c0c43ddd52418af134c551c92c9ea31e5ec942e" - integrity sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA== +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== dependencies: - "@jest/test-result" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.6.1" + jest-util "^29.7.0" string-length "^4.0.1" jest-worker@^26.5.0, jest-worker@^26.6.2: @@ -21345,25 +21376,25 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.1.tgz#64b015f0e985ef3a8ad049b61fe92b3db74a5319" - integrity sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA== +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== dependencies: "@types/node" "*" - jest-util "^29.6.1" + jest-util "^29.7.0" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.1.tgz#74be1cb719c3abe439f2d94aeb18e6540a5b02ad" - integrity sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw== +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== dependencies: - "@jest/core" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" import-local "^3.0.2" - jest-cli "^29.6.1" + jest-cli "^29.7.0" joi-to-json@^4.3.0: version "4.3.0" @@ -25725,7 +25756,7 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^29.0.0, pretty-format@^29.6.1, pretty-format@^29.7.0: +pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==