From 3ec981d03684634827779a882946b3fff90ddef2 Mon Sep 17 00:00:00 2001
From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
Date: Fri, 31 May 2024 13:54:41 +0200
Subject: [PATCH 01/46] [Fleet] include inactive agents in agent policy agent
count (#184517)
## Summary
Closes https://github.com/elastic/kibana/issues/180356
Include inactive agents in agent policy agent count to make it
consistent with the check that prevents deleting an agent policy.
To verify:
- create an agent policy with a short inactivity timeout e.g. 10 seconds
- enroll an agent and wait to become inactive
- go to agent policy details and verify the agent count shows 1 agent
- verify that the delete agent policy modal has a consistent count
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../components/agent_policy_delete_provider.tsx | 2 +-
.../fleet/server/routes/agent_policy/handlers.ts | 4 ++--
.../apis/agent_policy/agent_policy.ts | 10 ++++++++++
3 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx
index 0f7f2cdf45e5f..ce8cf468f9889 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx
@@ -176,7 +176,7 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({
>
{
const totalAgents = getAgentsByKuery(esClient, soClient, {
- showInactive: false,
+ showInactive: true,
perPage: 0,
page: 1,
kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`,
}).then(({ total }) => (agentPolicy.agents = total));
const unprivilegedAgents = getAgentsByKuery(esClient, soClient, {
- showInactive: false,
+ showInactive: true,
perPage: 0,
page: 1,
kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id} and ${UNPRIVILEGED_AGENT_KUERY}`,
diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts
index c3c912ddf7634..4ae4704eb5f7b 100644
--- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts
+++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts
@@ -1376,6 +1376,16 @@ export default function (providerContext: FtrProviderContext) {
policyWithInactiveAgents.id
);
+ // inactive agents are included in agent policy agents count
+ const {
+ body: {
+ item: { agents: agentsCount },
+ },
+ } = await supertest
+ .get(`/api/fleet/agent_policies/${policyWithInactiveAgents.id}`)
+ .expect(200);
+ expect(agentsCount).to.equal(1);
+
const { body } = await supertest
.post('/api/fleet/agent_policies/delete')
.set('kbn-xsrf', 'xxx')
From 83861dfaa901699e8ffab6cc093dbb669906b6c7 Mon Sep 17 00:00:00 2001
From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com>
Date: Fri, 31 May 2024 14:28:17 +0100
Subject: [PATCH 02/46] [Security Solution][Detection Engine] fix broken
serverless ES|QL tests (#184581)
## Summary
- https://github.com/elastic/kibana/pull/183096 PR disabled bfetch
requests for Serverless, leading to failure of multiple serverless ES|QL
tests that rely on intercepting `bsearch` request
- addresses:
- https://github.com/elastic/kibana/issues/184558
- https://github.com/elastic/kibana/issues/184556
- https://github.com/elastic/kibana/issues/184557
---
.../rule_creation/esql_rule.cy.ts | 3 +--
.../rule_edit/esql_rule.cy.ts | 4 +---
.../cypress/tasks/create_new_rule.ts | 20 ++++++++++++++-----
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts
index 918259747a21d..b8cebae392d38 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts
@@ -67,8 +67,7 @@ const workaroundForResizeObserver = () =>
}
});
-// Failing: See https://github.com/elastic/kibana/issues/184558
-describe.skip(
+describe(
'Detection ES|QL rules, creation',
{
// skipped in MKI as it depends on feature flag alertSuppressionForEsqlRuleEnabled
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts
index 25238dca9ebc4..a255ce289b1a7 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts
@@ -54,9 +54,7 @@ const expectedValidEsqlQuery =
// skipped in MKI as it depends on feature flag alertSuppressionForEsqlRuleEnabled
// alertSuppressionForEsqlRuleEnabled feature flag is also enabled in a global config
-// Failing: See https://github.com/elastic/kibana/issues/184556
-// Failing: See https://github.com/elastic/kibana/issues/184557
-describe.skip(
+describe(
'Detection ES|QL rules, edit',
{
tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
index 181f5dfa22eb1..d9f0120ab0199 100644
--- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts
@@ -953,11 +953,21 @@ export const interceptEsqlQueryFieldsRequest = (
esqlQuery: string,
alias: string = 'esqlQueryFields'
) => {
- cy.intercept('POST', '/internal/bsearch?*', (req) => {
- if (req.body?.batch?.[0]?.request?.params?.query?.includes?.(esqlQuery)) {
- req.alias = alias;
- }
- });
+ const isServerless = Cypress.env('IS_SERVERLESS');
+ // bfetch is disabled in serverless, so we need to watch another request
+ if (isServerless) {
+ cy.intercept('POST', '/internal/search/esql_async', (req) => {
+ if (req.body?.params?.query?.includes?.(esqlQuery)) {
+ req.alias = alias;
+ }
+ });
+ } else {
+ cy.intercept('POST', '/internal/bsearch?*', (req) => {
+ if (req.body?.batch?.[0]?.request?.params?.query?.includes?.(esqlQuery)) {
+ req.alias = alias;
+ }
+ });
+ }
};
export const checkLoadQueryDynamically = () => {
From f6bf142166f01e9d65c8cb6a309488870c7525ce Mon Sep 17 00:00:00 2001
From: Elastic Machine
Date: Fri, 31 May 2024 14:39:56 +0100
Subject: [PATCH 03/46] [main] Sync bundled packages with Package Storage
(#184287)
Automated by
https://buildkite.com/elastic/package-storage-infra-kibana-discover-release-branches/builds/745
---
fleet_packages.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fleet_packages.json b/fleet_packages.json
index 4f64dc77d5e89..d0e5d49e1dd1c 100644
--- a/fleet_packages.json
+++ b/fleet_packages.json
@@ -24,7 +24,7 @@
[
{
"name": "apm",
- "version": "8.13.1-preview-1708411360",
+ "version": "8.15.0-preview-1716438434",
"forceAlignStackVersion": true,
"allowSyncToPrerelease": true
},
From 1a217156e7308e74eb538c71ccb55bbaa261c4e0 Mon Sep 17 00:00:00 2001
From: Saikat Sarkar <132922331+saikatsarkar056@users.noreply.github.com>
Date: Fri, 31 May 2024 07:53:05 -0600
Subject: [PATCH 04/46] [Semantic Text UI] Deployment status modal appears one
last time even after the trained model deployment is complete (#183974)
If we continuously click the refresh button, the model deployment status
modal appears, which is the expected behavior. However, when the
deployment is complete, the modal appears one last time. We need to
prevent this final occurrence.
### Before Changes
https://github.com/elastic/kibana/assets/132922331/d2bd54dc-0809-438e-a245-3eab9edb5cac
### After Changes
https://github.com/elastic/kibana/assets/132922331/0dc17d5d-49ec-44cd-954a-7cdc95935331
### How to enable semantic text locally for testing these changes
Set isSemanticTextEnabled = true in this
[location](https://github.com/elastic/kibana/blob/3890189eee10555e67514ea8a3e9ca03b07eb887/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx#L72)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../trained_models_deployment_modal.test.tsx | 24 +++++--------------
.../details_page_mappings_content.tsx | 7 ++----
.../trained_models_deployment_modal.tsx | 18 +++++++-------
3 files changed, 17 insertions(+), 32 deletions(-)
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/trained_models_deployment_modal.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/trained_models_deployment_modal.test.tsx
index f02c3c6063b64..17299a0f04b4f 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/trained_models_deployment_modal.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/trained_models_deployment_modal.test.tsx
@@ -18,10 +18,10 @@ describe('When semantic_text is enabled', () => {
describe('When there is no error in the model deployment', () => {
const setup = registerTestBed(TrainedModelsDeploymentModal, {
defaultProps: {
- isSemanticTextEnabled: true,
- pendingDeployments: ['.elser-test-3'],
setIsModalVisible,
refreshModal,
+ pendingDeployments: ['.elser-test-3'],
+ errorsInTrainedModelDeployment: [],
},
memoryRouter: { wrapComponent: false },
});
@@ -48,17 +48,16 @@ describe('When semantic_text is enabled', () => {
await act(async () => {
find('confirmModalCancelButton').simulate('click');
});
- expect(setIsModalVisible.mock.calls).toHaveLength(1);
+ expect(setIsModalVisible).toHaveBeenLastCalledWith(false);
});
});
describe('When there is error in the model deployment', () => {
const setup = registerTestBed(TrainedModelsDeploymentModal, {
defaultProps: {
- isSemanticTextEnabled: true,
- pendingDeployments: ['.elser-test-3'],
setIsModalVisible: setIsVisibleForErrorModal,
refreshModal: tryAgainForErrorModal,
+ pendingDeployments: ['.elser-test-3'],
errorsInTrainedModelDeployment: ['.elser-test-3'],
},
memoryRouter: { wrapComponent: false },
@@ -82,22 +81,11 @@ describe('When semantic_text is enabled', () => {
expect(tryAgainForErrorModal.mock.calls).toHaveLength(1);
});
- it('should call setIsModalVisible method if cancel button is pressed', async () => {
+ it('should call setIsVisibleForErrorModal method if cancel button is pressed', async () => {
await act(async () => {
find('confirmModalCancelButton').simulate('click');
});
- expect(setIsVisibleForErrorModal.mock.calls).toHaveLength(1);
+ expect(setIsVisibleForErrorModal).toHaveBeenLastCalledWith(false);
});
});
});
-
-describe('When semantic_text is disabled', () => {
- const setup = registerTestBed(TrainedModelsDeploymentModal, {
- defaultProps: { isSemanticTextEnabled: false },
- memoryRouter: { wrapComponent: false },
- });
- const { exists } = setup();
- it('it should not display the modal', () => {
- expect(exists('trainedModelsDeploymentModal')).toBe(false);
- });
-});
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx
index b126f5b960a47..b8d2e3a5dc59f 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx
@@ -183,12 +183,10 @@ export const DetailsPageMappingsContent: FunctionComponent<{
}
await fetchInferenceToModelIdMap();
-
- setIsModalVisible(pendingDeployments.length > 0);
} catch (exception) {
setSaveMappingError(exception.message);
}
- }, [fetchInferenceToModelIdMap, isSemanticTextEnabled, pendingDeployments]);
+ }, [fetchInferenceToModelIdMap, isSemanticTextEnabled]);
const updateMappings = useCallback(async () => {
try {
@@ -557,11 +555,10 @@ export const DetailsPageMappingsContent: FunctionComponent<{
- {isModalVisible && (
+ {isModalVisible && isSemanticTextEnabled && (
void;
refreshModal: () => void;
pendingDeployments: Array;
@@ -25,7 +24,6 @@ const ML_APP_LOCATOR = 'ML_APP_LOCATOR';
const TRAINED_MODELS_MANAGE = 'trained_models';
export function TrainedModelsDeploymentModal({
- isSemanticTextEnabled,
setIsModalVisible,
refreshModal,
pendingDeployments = [],
@@ -36,6 +34,10 @@ export function TrainedModelsDeploymentModal({
const closeModal = () => setIsModalVisible(false);
const [mlManagementPageUrl, setMlManagementPageUrl] = useState('');
+ useEffect(() => {
+ setIsModalVisible(pendingDeployments.length > 0);
+ }, [pendingDeployments, setIsModalVisible]);
+
useEffect(() => {
let isCancelled = false;
const mlLocator = url?.locators.get(ML_APP_LOCATOR);
@@ -176,11 +178,9 @@ export function TrainedModelsDeploymentModal({
);
};
- return isSemanticTextEnabled ? (
- ErroredDeployments.length > 0 ? (
-
- ) : (
-
- )
- ) : null;
+ return ErroredDeployments.length > 0 ? (
+
+ ) : (
+
+ );
}
From f1c20decf89059ce3eacaff69237b74949baa29b Mon Sep 17 00:00:00 2001
From: Alexey Antonov
Date: Fri, 31 May 2024 17:02:44 +0300
Subject: [PATCH 05/46] fix: [Observability Overview][AXE-CORE] ARIA attributes
must conform to valid values validation error on button next to Log events
(#184512)
Closes: https://github.com/elastic/observability-dev/issues/3362
**Describe the bug:** The button next to Log events is not validating
against ARIA attributes must conform to valid values axe-core check.
### What was changed?:
1. set the correct `id` for `EuiAccordion`
### Screens:
---
.../pages/overview/components/sections/section_container.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/observability_solution/observability/public/pages/overview/components/sections/section_container.tsx b/x-pack/plugins/observability_solution/observability/public/pages/overview/components/sections/section_container.tsx
index a075e96caec01..58f6271da46ed 100644
--- a/x-pack/plugins/observability_solution/observability/public/pages/overview/components/sections/section_container.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/pages/overview/components/sections/section_container.tsx
@@ -13,6 +13,7 @@ import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
+ useGeneratedHtmlId,
} from '@elastic/eui';
import React from 'react';
import { useKibana } from '../../../../utils/kibana_react';
@@ -42,11 +43,13 @@ export function SectionContainer({
showExperimentalBadge = false,
}: Props) {
const { http } = useKibana().services;
+ const euiAccordionId = useGeneratedHtmlId({ prefix: 'euiAccordion' });
+
return (
Date: Fri, 31 May 2024 16:03:01 +0200
Subject: [PATCH 06/46] [CDN] Allow disabling CDN by passing "null" (#184578)
Close https://github.com/elastic/kibana/issues/184575
## How to test
1. Add `server.cdn.url: null` to kibana.dev.yml
2. Start ES + Kibana
3. Open Kibana, should work as usual by loading assets from Kibana
server
---
.../src/__snapshots__/http_config.test.ts.snap | 4 +++-
.../src/cdn_config/cdn_config.test.ts | 7 +++++++
.../src/cdn_config/cdn_config.ts | 4 ++--
.../core-http-server-internal/src/http_config.test.ts | 9 +++++++--
.../http/core-http-server-internal/src/http_config.ts | 4 +++-
.../src/static_assets/static_assets.test.ts | 6 ++++++
6 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap b/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap
index b6566b66bd00d..5dbbe1fd9ee2e 100644
--- a/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap
+++ b/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap
@@ -41,7 +41,9 @@ exports[`basePath throws if not specified, but rewriteBasePath is set 1`] = `"ca
exports[`has defaults for config 1`] = `
Object {
"autoListen": true,
- "cdn": Object {},
+ "cdn": Object {
+ "url": null,
+ },
"compression": Object {
"brotli": Object {
"enabled": false,
diff --git a/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.test.ts b/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.test.ts
index a728966b5e916..8a3de4b358ee9 100644
--- a/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.test.ts
+++ b/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.test.ts
@@ -54,4 +54,11 @@ describe('CdnConfig', () => {
const cdnConfig = CdnConfig.from({ url: '' });
expect(cdnConfig.getCspConfig()).toEqual({});
});
+
+ it('accepts "null" URL', () => {
+ const cdnConfig = CdnConfig.from({ url: null });
+ expect(cdnConfig.baseHref).toBeUndefined();
+ expect(cdnConfig.host).toBeUndefined();
+ expect(cdnConfig.getCspConfig()).toEqual({});
+ });
});
diff --git a/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.ts b/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.ts
index 68a255a62f5c2..c4969bdfd60e3 100644
--- a/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.ts
+++ b/packages/core/http/core-http-server-internal/src/cdn_config/cdn_config.ts
@@ -10,12 +10,12 @@ import { URL, format } from 'node:url';
import type { CspAdditionalConfig } from '../csp';
export interface Input {
- url?: string;
+ url?: null | string;
}
export class CdnConfig {
private readonly url: undefined | URL;
- constructor(url?: string) {
+ constructor(url?: null | string) {
if (url) {
this.url = new URL(url); // This will throw for invalid URLs, although should be validated before reaching this point
}
diff --git a/packages/core/http/core-http-server-internal/src/http_config.test.ts b/packages/core/http/core-http-server-internal/src/http_config.test.ts
index 6cc9042b14b9c..d2bac7e8cf1c0 100644
--- a/packages/core/http/core-http-server-internal/src/http_config.test.ts
+++ b/packages/core/http/core-http-server-internal/src/http_config.test.ts
@@ -549,9 +549,14 @@ describe('cdn', () => {
cdn: { url: 'https://cdn.example.com' },
});
});
+ it('can be "unset" using "null"', () => {
+ expect(config.schema.validate({ cdn: { url: null } })).toMatchObject({
+ cdn: { url: null },
+ });
+ });
it.each([['foo'], ['http:./']])('throws for invalid URL %s', (url) => {
- expect(() => config.schema.validate({ cdn: { url } })).toThrowErrorMatchingInlineSnapshot(
- `"[cdn.url]: expected URI with scheme [http|https]."`
+ expect(() => config.schema.validate({ cdn: { url } })).toThrow(
+ /expected URI with scheme \[http\|https\]/
);
});
it.each([
diff --git a/packages/core/http/core-http-server-internal/src/http_config.ts b/packages/core/http/core-http-server-internal/src/http_config.ts
index ac5072958a512..06e021c8acdb9 100644
--- a/packages/core/http/core-http-server-internal/src/http_config.ts
+++ b/packages/core/http/core-http-server-internal/src/http_config.ts
@@ -80,7 +80,9 @@ const configSchema = schema.object(
},
}),
cdn: schema.object({
- url: schema.maybe(schema.uri({ scheme: ['http', 'https'], validate: validateCdnURL })),
+ url: schema.nullable(
+ schema.maybe(schema.uri({ scheme: ['http', 'https'], validate: validateCdnURL }))
+ ),
}),
oas: schema.object({
enabled: schema.boolean({ defaultValue: false }),
diff --git a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts
index 9d2c58e85b8ae..f1c4d247cec70 100644
--- a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts
+++ b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts
@@ -46,6 +46,12 @@ describe('StaticAssets', () => {
staticAssets = new StaticAssets(args);
expect(staticAssets.isUsingCdn()).toBe(true);
});
+
+ it('returns false when CDN config contains "null" URL', () => {
+ args.cdnConfig = CdnConfig.from({ url: null });
+ staticAssets = new StaticAssets(args);
+ expect(staticAssets.isUsingCdn()).toBe(false);
+ });
});
describe('#getPluginAssetHref()', () => {
From 03a946614e113d94b0ad5d3bfe494766de868206 Mon Sep 17 00:00:00 2001
From: Steph Milovic
Date: Fri, 31 May 2024 08:19:27 -0600
Subject: [PATCH 07/46] [Security solution] Name telemetry data views with
static ids (#184534)
---
.../scripts => scripts/telemetry}/README.md | 0
.../telemetry}/build_ebt_data_view.sh | 0
.../telemetry}/build_ebt_data_view.ts | 20 ++++++++-----------
...ecurity_solution_ebt_kibana_browser.ndjson | 4 ++--
...security_solution_ebt_kibana_server.ndjson | 4 ++--
5 files changed, 12 insertions(+), 16 deletions(-)
rename x-pack/plugins/security_solution/{public/common/lib/telemetry/scripts => scripts/telemetry}/README.md (100%)
rename x-pack/plugins/security_solution/{public/common/lib/telemetry/scripts => scripts/telemetry}/build_ebt_data_view.sh (100%)
rename x-pack/plugins/security_solution/{public/common/lib/telemetry/scripts => scripts/telemetry}/build_ebt_data_view.ts (90%)
rename x-pack/plugins/security_solution/{public/common/lib/telemetry/scripts => scripts/telemetry}/saved_objects/security_solution_ebt_kibana_browser.ndjson (96%)
rename x-pack/plugins/security_solution/{public/common/lib/telemetry/scripts => scripts/telemetry}/saved_objects/security_solution_ebt_kibana_server.ndjson (62%)
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/README.md b/x-pack/plugins/security_solution/scripts/telemetry/README.md
similarity index 100%
rename from x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/README.md
rename to x-pack/plugins/security_solution/scripts/telemetry/README.md
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/build_ebt_data_view.sh b/x-pack/plugins/security_solution/scripts/telemetry/build_ebt_data_view.sh
similarity index 100%
rename from x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/build_ebt_data_view.sh
rename to x-pack/plugins/security_solution/scripts/telemetry/build_ebt_data_view.sh
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/build_ebt_data_view.ts b/x-pack/plugins/security_solution/scripts/telemetry/build_ebt_data_view.ts
similarity index 90%
rename from x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/build_ebt_data_view.ts
rename to x-pack/plugins/security_solution/scripts/telemetry/build_ebt_data_view.ts
index 5a7dbc605a969..e17b72d195c6c 100755
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/build_ebt_data_view.ts
+++ b/x-pack/plugins/security_solution/scripts/telemetry/build_ebt_data_view.ts
@@ -9,8 +9,8 @@ import { ToolingLog } from '@kbn/tooling-log';
import axios from 'axios';
import { events as genAiEvents } from '@kbn/elastic-assistant-plugin/server/lib/telemetry/event_based_telemetry';
-import { events as securityEvents } from '../../../../../server/lib/telemetry/event_based/events';
-import { telemetryEvents } from '../events/telemetry_events';
+import { events as securityEvents } from '../../server/lib/telemetry/event_based/events';
+import { telemetryEvents } from '../../public/common/lib/telemetry/events/telemetry_events';
// uncomment and add to run script, but do not commit as creates cirular dependency
// import { telemetryEvents as serverlessEvents } from '@kbn/security-solution-serverless/server/telemetry/event_based_telemetry';
@@ -55,18 +55,17 @@ async function cli(): Promise {
'kbn-xsrf': 'xxx',
'Content-Type': 'application/json',
};
- const dataViewApiUrl = `${removeTrailingSlash(kibanaUrl)}/s/${spaceId}/api/data_views`;
+ const dataViewApiUrl = `${removeTrailingSlash(
+ kibanaUrl
+ )}/s/${spaceId}/api/data_views/data_view/${dataViewName}`;
try {
logger.info(`Fetching data view "${dataViewName}"...`);
const {
- data: { data_view: dataViews },
+ data: { data_view: ourDataView },
} = await axios.get(dataViewApiUrl, {
headers: requestHeaders,
});
- const ourDataView = dataViews.find(
- (dataView: { id: string; name: string }) => dataView.name === dataViewName
- );
if (!ourDataView) {
throw new Error(
@@ -111,7 +110,7 @@ async function cli(): Promise {
});
});
- const runtimeFieldUrl = `${dataViewApiUrl}/data_view/${ourDataView.id}/runtime_field`;
+ const runtimeFieldUrl = `${dataViewApiUrl}/runtime_field`;
await upsertRuntimeFields(runtimeFields, runtimeFieldUrl, requestHeaders);
const manualFieldLength = Object.keys(manualRuntimeFields).length;
const runtimeFieldLength = Object.keys(runtimeFields).length;
@@ -200,10 +199,7 @@ async function upsertRuntimeFields(
headers: requestHeaders,
});
} catch (error) {
- throw new Error(
- `Error upserting field ${fieldName}:`,
- error.response ? error.response.data : error.message
- );
+ throw new Error(`Error upserting field '${fieldName}: ${fieldType}' - ${error.message}`);
}
}
}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/saved_objects/security_solution_ebt_kibana_browser.ndjson b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_browser.ndjson
similarity index 96%
rename from x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/saved_objects/security_solution_ebt_kibana_browser.ndjson
rename to x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_browser.ndjson
index 012b01d1820f9..be4eb8f1e7785 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/saved_objects/security_solution_ebt_kibana_browser.ndjson
+++ b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_browser.ndjson
@@ -1,2 +1,2 @@
-{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.groupingId\":{\"count\":1},\"properties.target\":{\"count\":1},\"properties.groupName\":{\"count\":2},\"properties.metadata.telemetry.component\":{\"count\":2},\"properties.unallowedMappingFields\":{\"count\":2},\"properties.unallowedValueFields\":{\"count\":1},\"context.labels.serverless\":{\"count\":4},\"properties.tableId\":{\"count\":1},\"properties.groupNumber\":{\"count\":1},\"properties.groupByField\":{\"count\":4},\"properties.status\":{\"count\":1},\"properties.conversationId\":{\"count\":17},\"properties.invokedBy\":{\"count\":7},\"properties.role\":{\"count\":3},\"properties.isEnabledKnowledgeBase\":{\"count\":1},\"properties.isEnabledRAGAlerts\":{\"count\":1},\"properties.promptTitle\":{\"count\":3},\"properties.fieldName\":{\"count\":1},\"properties.actionId\":{\"count\":1},\"properties.displayName\":{\"count\":1},\"properties.batchId\":{\"count\":8},\"properties.indexId\":{\"count\":1},\"properties.indexName\":{\"count\":2},\"properties.numberOfIndices\":{\"count\":1},\"properties.timeConsumedMs\":{\"count\":1},\"properties.ecsVersion\":{\"count\":1},\"properties.errorCount\":{\"count\":1},\"properties.numberOfIncompatibleFields\":{\"count\":1},\"properties.numberOfDocuments\":{\"count\":1},\"properties.sizeInBytes\":{\"count\":4},\"properties.isCheckAll\":{\"count\":5},\"properties.ilmPhase\":{\"count\":2},\"properties.title\":{\"count\":1},\"properties.location\":{\"count\":1},\"context.applicationId\":{\"count\":6},\"context.cloudId\":{\"count\":6},\"context.cluster_name\":{\"count\":13},\"context.cluster_uuid\":{\"count\":28},\"context.cluster_version\":{\"count\":2},\"context.license_type\":{\"count\":1},\"context.page\":{\"count\":8},\"context.pageName\":{\"count\":6},\"context.page_title\":{\"count\":1},\"context.page_url\":{\"count\":1},\"context.session_id\":{\"count\":2},\"event_type\":{\"count\":36},\"properties\":{\"count\":8},\"properties.pattern\":{\"count\":2},\"peoperties.indexName\":{\"count\":1},\"properties.stepId\":{},\"properties.trigger\":{},\"properties.stepLinkId\":{},\"properties.originStepId\":{},\"properties.durationMs\":{},\"properties.isOpen\":{},\"properties.actionTypeId\":{},\"properties.model\":{},\"properties.provider\":{},\"properties.assistantStreamingEnabled\":{},\"properties.alertsContextCount\":{},\"properties.alertsCount\":{},\"properties.configuredAlertsCount\":{},\"properties.entity\":{},\"properties.selectedSeverity\":{},\"properties.file.size\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.stats.validLines\":{},\"properties.stats.invalidLines\":{},\"properties.stats.totalLines\":{},\"properties.valid\":{},\"properties.errorCode\":{},\"properties.action\":{},\"properties.quantity\":{},\"properties.jobId\":{},\"properties.isElasticJob\":{},\"properties.moduleId\":{},\"properties.errorMessage\":{},\"properties.count\":{},\"properties.numberOfIndicesChecked\":{},\"properties.numberOfSameFamily\":{},\"properties.numberOfFields\":{},\"properties.numberOfEcsFields\":{},\"properties.numberOfCustomFields\":{},\"properties.panel\":{},\"properties.tabId\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-browser","runtimeFieldMap":"{\"properties.groupingId\":{\"type\":\"keyword\"},\"properties.target\":{\"type\":\"keyword\"},\"property.stackByField\":{\"type\":\"keyword\"},\"properties.groupName\":{\"type\":\"keyword\"},\"context.prebuiltRulesPackageVersion\":{\"type\":\"keyword\"},\"properties.metadata.telemetry.component\":{\"type\":\"keyword\"},\"properties.unallowedMappingFields\":{\"type\":\"keyword\"},\"properties.unallowedValueFields\":{\"type\":\"keyword\"},\"context.labels.serverless\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"day_of_week\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault()))\"}},\"properties.isOpen\":{\"type\":\"boolean\"},\"properties.tableId\":{\"type\":\"keyword\"},\"properties.groupNumber\":{\"type\":\"long\"},\"properties.groupByField\":{\"type\":\"keyword\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.conversationId\":{\"type\":\"keyword\"},\"properties.invokedBy\":{\"type\":\"keyword\"},\"properties.role\":{\"type\":\"keyword\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.model\":{\"type\":\"keyword\"},\"properties.provider\":{\"type\":\"keyword\"},\"properties.promptTitle\":{\"type\":\"keyword\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.durationMs\":{\"type\":\"long\"},\"properties.alertsContextCount\":{\"type\":\"long\"},\"properties.alertsCount\":{\"type\":\"long\"},\"properties.configuredAlertsCount\":{\"type\":\"long\"},\"properties.entity\":{\"type\":\"keyword\"},\"properties.selectedSeverity\":{\"type\":\"keyword\"},\"properties.file.size\":{\"type\":\"long\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.stats.validLines\":{\"type\":\"long\"},\"properties.stats.invalidLines\":{\"type\":\"long\"},\"properties.stats.totalLines\":{\"type\":\"long\"},\"properties.valid\":{\"type\":\"boolean\"},\"properties.errorCode\":{\"type\":\"keyword\"},\"properties.action\":{\"type\":\"keyword\"},\"properties.quantity\":{\"type\":\"long\"},\"properties.jobId\":{\"type\":\"keyword\"},\"properties.isElasticJob\":{\"type\":\"boolean\"},\"properties.moduleId\":{\"type\":\"keyword\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.fieldName\":{\"type\":\"keyword\"},\"properties.actionId\":{\"type\":\"keyword\"},\"properties.displayName\":{\"type\":\"keyword\"},\"properties.count\":{\"type\":\"long\"},\"properties.batchId\":{\"type\":\"keyword\"},\"properties.indexId\":{\"type\":\"keyword\"},\"properties.indexName\":{\"type\":\"keyword\"},\"properties.numberOfIndices\":{\"type\":\"long\"},\"properties.numberOfIndicesChecked\":{\"type\":\"long\"},\"properties.numberOfSameFamily\":{\"type\":\"long\"},\"properties.timeConsumedMs\":{\"type\":\"long\"},\"properties.ecsVersion\":{\"type\":\"keyword\"},\"properties.errorCount\":{\"type\":\"long\"},\"properties.numberOfFields\":{\"type\":\"long\"},\"properties.numberOfIncompatibleFields\":{\"type\":\"long\"},\"properties.numberOfEcsFields\":{\"type\":\"long\"},\"properties.numberOfCustomFields\":{\"type\":\"long\"},\"properties.numberOfDocuments\":{\"type\":\"long\"},\"properties.sizeInBytes\":{\"type\":\"long\"},\"properties.isCheckAll\":{\"type\":\"boolean\"},\"properties.ilmPhase\":{\"type\":\"keyword\"},\"properties.title\":{\"type\":\"keyword\"},\"properties.location\":{\"type\":\"keyword\"},\"properties.panel\":{\"type\":\"keyword\"},\"properties.tabId\":{\"type\":\"keyword\"},\"properties.stepId\":{\"type\":\"keyword\"},\"properties.trigger\":{\"type\":\"keyword\"},\"properties.originStepId\":{\"type\":\"keyword\"},\"properties.stepLinkId\":{\"type\":\"keyword\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-browser","typeMeta":"{}"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-01T15:19:22.771Z","id":"d00907cf-7da0-4ab7-8e1d-9909145362cf","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-24T17:39:31.426Z","version":"WzI5NjYzLDVd"}
-{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
\ No newline at end of file
+{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.groupingId\":{\"count\":1},\"properties.target\":{\"count\":1},\"properties.groupName\":{\"count\":2},\"properties.metadata.telemetry.component\":{\"count\":2},\"properties.unallowedMappingFields\":{\"count\":2},\"properties.unallowedValueFields\":{\"count\":1},\"context.labels.serverless\":{\"count\":4},\"properties.tableId\":{\"count\":1},\"properties.groupNumber\":{\"count\":1},\"properties.groupByField\":{\"count\":4},\"properties.status\":{\"count\":1},\"properties.conversationId\":{\"count\":17},\"properties.invokedBy\":{\"count\":7},\"properties.role\":{\"count\":3},\"properties.isEnabledKnowledgeBase\":{\"count\":1},\"properties.isEnabledRAGAlerts\":{\"count\":1},\"properties.promptTitle\":{\"count\":3},\"properties.fieldName\":{\"count\":1},\"properties.actionId\":{\"count\":1},\"properties.displayName\":{\"count\":1},\"properties.batchId\":{\"count\":8},\"properties.indexId\":{\"count\":1},\"properties.indexName\":{\"count\":2},\"properties.numberOfIndices\":{\"count\":1},\"properties.timeConsumedMs\":{\"count\":1},\"properties.ecsVersion\":{\"count\":1},\"properties.errorCount\":{\"count\":1},\"properties.numberOfIncompatibleFields\":{\"count\":1},\"properties.numberOfDocuments\":{\"count\":1},\"properties.sizeInBytes\":{\"count\":4},\"properties.isCheckAll\":{\"count\":5},\"properties.ilmPhase\":{\"count\":2},\"properties.title\":{\"count\":1},\"properties.location\":{\"count\":1},\"context.applicationId\":{\"count\":6},\"context.cloudId\":{\"count\":6},\"context.cluster_name\":{\"count\":13},\"context.cluster_uuid\":{\"count\":28},\"context.cluster_version\":{\"count\":2},\"context.license_type\":{\"count\":1},\"context.page\":{\"count\":8},\"context.pageName\":{\"count\":6},\"context.page_title\":{\"count\":1},\"context.page_url\":{\"count\":1},\"context.session_id\":{\"count\":2},\"event_type\":{\"count\":36},\"properties\":{\"count\":8},\"properties.pattern\":{\"count\":2},\"peoperties.indexName\":{\"count\":1},\"properties.stepId\":{},\"properties.trigger\":{},\"properties.stepLinkId\":{},\"properties.originStepId\":{},\"properties.durationMs\":{},\"properties.isOpen\":{},\"properties.actionTypeId\":{},\"properties.model\":{},\"properties.provider\":{},\"properties.assistantStreamingEnabled\":{},\"properties.alertsContextCount\":{},\"properties.alertsCount\":{},\"properties.configuredAlertsCount\":{},\"properties.entity\":{},\"properties.selectedSeverity\":{},\"properties.file.size\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.stats.validLines\":{},\"properties.stats.invalidLines\":{},\"properties.stats.totalLines\":{},\"properties.valid\":{},\"properties.errorCode\":{},\"properties.action\":{},\"properties.quantity\":{},\"properties.jobId\":{},\"properties.isElasticJob\":{},\"properties.moduleId\":{},\"properties.errorMessage\":{},\"properties.count\":{},\"properties.numberOfIndicesChecked\":{},\"properties.numberOfSameFamily\":{},\"properties.numberOfFields\":{},\"properties.numberOfEcsFields\":{},\"properties.numberOfCustomFields\":{},\"properties.panel\":{},\"properties.tabId\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-browser","runtimeFieldMap":"{\"properties.groupingId\":{\"type\":\"keyword\"},\"properties.target\":{\"type\":\"keyword\"},\"property.stackByField\":{\"type\":\"keyword\"},\"properties.groupName\":{\"type\":\"keyword\"},\"context.prebuiltRulesPackageVersion\":{\"type\":\"keyword\"},\"properties.metadata.telemetry.component\":{\"type\":\"keyword\"},\"properties.unallowedMappingFields\":{\"type\":\"keyword\"},\"properties.unallowedValueFields\":{\"type\":\"keyword\"},\"context.labels.serverless\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"day_of_week\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault()))\"}},\"properties.isOpen\":{\"type\":\"boolean\"},\"properties.tableId\":{\"type\":\"keyword\"},\"properties.groupNumber\":{\"type\":\"long\"},\"properties.groupByField\":{\"type\":\"keyword\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.conversationId\":{\"type\":\"keyword\"},\"properties.invokedBy\":{\"type\":\"keyword\"},\"properties.role\":{\"type\":\"keyword\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.model\":{\"type\":\"keyword\"},\"properties.provider\":{\"type\":\"keyword\"},\"properties.promptTitle\":{\"type\":\"keyword\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.durationMs\":{\"type\":\"long\"},\"properties.alertsContextCount\":{\"type\":\"long\"},\"properties.alertsCount\":{\"type\":\"long\"},\"properties.configuredAlertsCount\":{\"type\":\"long\"},\"properties.entity\":{\"type\":\"keyword\"},\"properties.selectedSeverity\":{\"type\":\"keyword\"},\"properties.file.size\":{\"type\":\"long\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.stats.validLines\":{\"type\":\"long\"},\"properties.stats.invalidLines\":{\"type\":\"long\"},\"properties.stats.totalLines\":{\"type\":\"long\"},\"properties.valid\":{\"type\":\"boolean\"},\"properties.errorCode\":{\"type\":\"keyword\"},\"properties.action\":{\"type\":\"keyword\"},\"properties.quantity\":{\"type\":\"long\"},\"properties.jobId\":{\"type\":\"keyword\"},\"properties.isElasticJob\":{\"type\":\"boolean\"},\"properties.moduleId\":{\"type\":\"keyword\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.fieldName\":{\"type\":\"keyword\"},\"properties.actionId\":{\"type\":\"keyword\"},\"properties.displayName\":{\"type\":\"keyword\"},\"properties.count\":{\"type\":\"long\"},\"properties.batchId\":{\"type\":\"keyword\"},\"properties.indexId\":{\"type\":\"keyword\"},\"properties.indexName\":{\"type\":\"keyword\"},\"properties.numberOfIndices\":{\"type\":\"long\"},\"properties.numberOfIndicesChecked\":{\"type\":\"long\"},\"properties.numberOfSameFamily\":{\"type\":\"long\"},\"properties.timeConsumedMs\":{\"type\":\"long\"},\"properties.ecsVersion\":{\"type\":\"keyword\"},\"properties.errorCount\":{\"type\":\"long\"},\"properties.numberOfFields\":{\"type\":\"long\"},\"properties.numberOfIncompatibleFields\":{\"type\":\"long\"},\"properties.numberOfEcsFields\":{\"type\":\"long\"},\"properties.numberOfCustomFields\":{\"type\":\"long\"},\"properties.numberOfDocuments\":{\"type\":\"long\"},\"properties.sizeInBytes\":{\"type\":\"long\"},\"properties.isCheckAll\":{\"type\":\"boolean\"},\"properties.ilmPhase\":{\"type\":\"keyword\"},\"properties.title\":{\"type\":\"keyword\"},\"properties.location\":{\"type\":\"keyword\"},\"properties.panel\":{\"type\":\"keyword\"},\"properties.tabId\":{\"type\":\"keyword\"},\"properties.stepId\":{\"type\":\"keyword\"},\"properties.trigger\":{\"type\":\"keyword\"},\"properties.originStepId\":{\"type\":\"keyword\"},\"properties.stepLinkId\":{\"type\":\"keyword\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-browser","typeMeta":"{}"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-30T16:12:33.003Z","id":"security-solution-ebt-kibana-browser","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-30T16:52:03.990Z","version":"WzMwNTU0LDVd"}
+{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/saved_objects/security_solution_ebt_kibana_server.ndjson b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson
similarity index 62%
rename from x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/saved_objects/security_solution_ebt_kibana_server.ndjson
rename to x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson
index fce8b2c7d284e..ee1293900f4ec 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/scripts/saved_objects/security_solution_ebt_kibana_server.ndjson
+++ b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson
@@ -1,2 +1,2 @@
-{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.model\":{},\"properties.resourceAccessed\":{},\"properties.resultCount\":{},\"properties.responseTime\":{},\"properties.errorMessage\":{},\"properties.isEnabledKnowledgeBase\":{},\"properties.isEnabledRAGAlerts\":{},\"properties.assistantStreamingEnabled\":{},\"properties.actionTypeId\":{},\"properties.message\":{},\"properties.productTier\":{},\"properties.failedToDeleteCount\":{},\"properties.totalInstalledCount\":{},\"properties.scoresWritten\":{},\"properties.taskDurationInSeconds\":{},\"properties.interval\":{},\"properties.alertSampleSizePerShard\":{},\"properties.status\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.result.successful\":{},\"properties.result.failed\":{},\"properties.result.total\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-server","runtimeFieldMap":"{\"properties.model\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.message\":{\"type\":\"keyword\"},\"properties.productTier\":{\"type\":\"keyword\"},\"properties.failedToDeleteCount\":{\"type\":\"long\"},\"properties.totalInstalledCount\":{\"type\":\"long\"},\"properties.scoresWritten\":{\"type\":\"long\"},\"properties.taskDurationInSeconds\":{\"type\":\"long\"},\"properties.interval\":{\"type\":\"keyword\"},\"properties.alertSampleSizePerShard\":{\"type\":\"long\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.result.successful\":{\"type\":\"long\"},\"properties.result.failed\":{\"type\":\"long\"},\"properties.result.total\":{\"type\":\"long\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-server"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-24T16:08:01.010Z","id":"1f69f020-6e7d-4d19-bbad-0b052bddf552","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-24T16:12:57.199Z","version":"WzI5NDY2LDVd"}
-{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
\ No newline at end of file
+{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.model\":{},\"properties.resourceAccessed\":{},\"properties.resultCount\":{},\"properties.responseTime\":{},\"properties.errorMessage\":{},\"properties.isEnabledKnowledgeBase\":{},\"properties.isEnabledRAGAlerts\":{},\"properties.assistantStreamingEnabled\":{},\"properties.actionTypeId\":{},\"properties.message\":{},\"properties.productTier\":{},\"properties.failedToDeleteCount\":{},\"properties.totalInstalledCount\":{},\"properties.scoresWritten\":{},\"properties.taskDurationInSeconds\":{},\"properties.interval\":{},\"properties.alertSampleSizePerShard\":{},\"properties.status\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.result.successful\":{},\"properties.result.failed\":{},\"properties.result.total\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-server","runtimeFieldMap":"{\"properties.message\":{\"type\":\"keyword\"},\"properties.productTier\":{\"type\":\"keyword\"},\"properties.failedToDeleteCount\":{\"type\":\"long\"},\"properties.totalInstalledCount\":{\"type\":\"long\"},\"properties.model\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.scoresWritten\":{\"type\":\"long\"},\"properties.taskDurationInSeconds\":{\"type\":\"long\"},\"properties.interval\":{\"type\":\"keyword\"},\"properties.alertSampleSizePerShard\":{\"type\":\"long\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.result.successful\":{\"type\":\"long\"},\"properties.result.failed\":{\"type\":\"long\"},\"properties.result.total\":{\"type\":\"long\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-server"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-30T16:12:44.874Z","id":"security-solution-ebt-kibana-server","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-30T16:52:11.038Z","version":"WzMwNTYxLDVd"}
+{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
From 839d611557377164d7801924f8b6cec415df8d61 Mon Sep 17 00:00:00 2001
From: Alexi Doak <109488926+doakalexi@users.noreply.github.com>
Date: Fri, 31 May 2024 07:20:12 -0700
Subject: [PATCH 08/46] =?UTF-8?q?[ResponseOps]=20Flaky=20test=20x-pack/tes?=
=?UTF-8?q?t/alerting=5Fapi=5Fintegration/spaces=5Fonly/tests/action=5Ftas?=
=?UTF-8?q?k=5Fparams/migrations=C2=B7ts=20(#184473)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Resolves https://github.com/elastic/kibana/issues/154358
## Summary
I couldn't get this to fail, but it's using `esArchiver` which is known
to be flaky. I used the info in this issue, [Proactively improve
robustness of FTR tests that use esArchiver
#161882](https://github.com/elastic/kibana/issues/161882), to maybe help
reduce the flakiness.
Flaky test runner:
[run
1](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6163)
x 50
[run
2](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6164)
x 200
[run
3](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6165)
x 200
---
.../tests/action_task_params/migrations.ts | 3 +-
.../es_archives/action_task_params/data.json | 4 +-
.../action_task_params/mappings.json | 2526 -----------------
3 files changed, 3 insertions(+), 2530 deletions(-)
delete mode 100644 x-pack/test/functional/es_archives/action_task_params/mappings.json
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts
index 2c9147ac2dfb5..c7c9611b21312 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts
@@ -16,8 +16,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
const es = getService('es');
const esArchiver = getService('esArchiver');
- // FLAKY: https://github.com/elastic/kibana/issues/154358
- describe.skip('migrations', () => {
+ describe('migrations', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/action_task_params');
});
diff --git a/x-pack/test/functional/es_archives/action_task_params/data.json b/x-pack/test/functional/es_archives/action_task_params/data.json
index 158faa429d888..3b9ae989ff98a 100644
--- a/x-pack/test/functional/es_archives/action_task_params/data.json
+++ b/x-pack/test/functional/es_archives/action_task_params/data.json
@@ -1,7 +1,7 @@
{
"type": "doc",
"value": {
- "index": ".kibana_1",
+ "index": ".kibana_alerting_cases",
"id": "action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed",
"source": {
"type": "action_task_params",
@@ -33,7 +33,7 @@
{
"type": "doc",
"value": {
- "index": ".kibana_1",
+ "index": ".kibana_alerting_cases",
"id": "action_task_params:0205a520-0054-11ec-917b-f7aa317691ed",
"source": {
"type": "action_task_params",
diff --git a/x-pack/test/functional/es_archives/action_task_params/mappings.json b/x-pack/test/functional/es_archives/action_task_params/mappings.json
deleted file mode 100644
index d28c1504d3eed..0000000000000
--- a/x-pack/test/functional/es_archives/action_task_params/mappings.json
+++ /dev/null
@@ -1,2526 +0,0 @@
-{
- "type": "index",
- "value": {
- "aliases": {
- ".kibana": {
- }
- },
- "index": ".kibana_1",
- "mappings": {
- "_meta": {
- "migrationMappingPropertyHashes": {
- "action": "6e96ac5e648f57523879661ea72525b7",
- "action_task_params": "a9d49f184ee89641044be0ca2950fa3a",
- "alert": "7b44fba6773e37c806ce290ea9b7024e",
- "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd",
- "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724",
- "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724",
- "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd",
- "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724",
- "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724",
- "canvas-element": "7390014e1091044523666d97247392fc",
- "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231",
- "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715",
- "cases": "32aa96a6d3855ddda53010ae2048ac22",
- "cases-comments": "c2061fb929f585df57425102fa928b4b",
- "cases-configure": "42711cbb311976c0687853f4c1354572",
- "cases-user-actions": "32277330ec6b721abe3b846cfd939a71",
- "config": "c63748b75f39d0c54de12d12c1ccbc20",
- "dashboard": "d00f614b29a80360e1190193fd333bab",
- "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0",
- "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862",
- "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff",
- "exception-list": "497afa2f881a675d72d58e20057f3d8b",
- "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b",
- "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e",
- "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec",
- "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2",
- "fleet-agents": "034346488514b7058a79140b19ddf631",
- "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f",
- "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1",
- "index-pattern": "66eccb05066c5a89924f48a9e9736499",
- "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c",
- "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c",
- "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3",
- "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749",
- "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09",
- "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2",
- "kql-telemetry": "d12a98a6f19a2d273696597547e064ee",
- "lens": "d33c68a69ff1e78c9888dedd2164ac22",
- "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327",
- "map": "4a05b35c3a3a58fbc72dd0202dc3487f",
- "maps-telemetry": "5ef305b18111b77789afefbd36b66171",
- "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187",
- "migrationVersion": "4a1746014a75ade3a714e1db5763276f",
- "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9",
- "namespace": "2f4316de49999235636386fe51dc06c1",
- "namespaces": "2f4316de49999235636386fe51dc06c1",
- "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9",
- "references": "7997cf5a56cc02bdc9c93361bde732b0",
- "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4",
- "search": "5c4b9a6effceb17ae8a0ab22d0c49767",
- "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724",
- "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18",
- "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0",
- "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421",
- "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084",
- "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29",
- "space": "c5ca8acafa0beaa4d08d014a97b6bc6b",
- "telemetry": "36a616f7026dfa617d6655df850fe16d",
- "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215",
- "type": "2f4316de49999235636386fe51dc06c1",
- "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3",
- "updated_at": "00da57df13e94e9d98437d13ace4bfe0",
- "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763",
- "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b",
- "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80",
- "url": "c7f66a0df8b1b52f17c28c4adb111105",
- "visualization": "52d7a13ad68a150c4525b292d23e12cc",
- "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724"
- }
- },
- "dynamic": "strict",
- "properties": {
- "action": {
- "properties": {
- "actionTypeId": {
- "type": "keyword"
- },
- "isMissingSecrets": {
- "type": "boolean"
- },
- "config": {
- "enabled": false,
- "type": "object"
- },
- "name": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "secrets": {
- "type": "binary"
- }
- }
- },
- "action_task_params": {
- "properties": {
- "actionId": {
- "type": "keyword"
- },
- "apiKey": {
- "type": "binary"
- },
- "params": {
- "enabled": false,
- "type": "object"
- },
- "relatedSavedObjects": {
- "enabled": false,
- "type": "object"
- }
- }
- },
- "alert": {
- "properties": {
- "actions": {
- "properties": {
- "actionRef": {
- "type": "keyword"
- },
- "actionTypeId": {
- "type": "keyword"
- },
- "group": {
- "type": "keyword"
- },
- "params": {
- "enabled": false,
- "type": "object"
- }
- },
- "type": "nested"
- },
- "alertTypeId": {
- "type": "keyword"
- },
- "apiKey": {
- "type": "binary"
- },
- "apiKeyOwner": {
- "type": "keyword"
- },
- "consumer": {
- "type": "keyword"
- },
- "createdAt": {
- "type": "date"
- },
- "createdBy": {
- "type": "keyword"
- },
- "enabled": {
- "type": "boolean"
- },
- "muteAll": {
- "type": "boolean"
- },
- "mutedInstanceIds": {
- "type": "keyword"
- },
- "name": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "params": {
- "enabled": false,
- "type": "object"
- },
- "schedule": {
- "properties": {
- "interval": {
- "type": "keyword"
- }
- }
- },
- "scheduledTaskId": {
- "type": "keyword"
- },
- "tags": {
- "type": "keyword"
- },
- "throttle": {
- "type": "keyword"
- },
- "updatedBy": {
- "type": "keyword"
- }
- }
- },
- "apm-indices": {
- "properties": {
- "error": {
- "type": "keyword"
- },
- "metric": {
- "type": "keyword"
- },
- "onboarding": {
- "type": "keyword"
- },
- "sourcemap": {
- "type": "keyword"
- },
- "span": {
- "type": "keyword"
- },
- "transaction": {
- "type": "keyword"
- }
- }
- },
- "apm-telemetry": {
- "dynamic": "false",
- "type": "object"
- },
- "app_search_telemetry": {
- "dynamic": "false",
- "type": "object"
- },
- "application_usage_daily": {
- "dynamic": "false",
- "properties": {
- "timestamp": {
- "type": "date"
- }
- }
- },
- "application_usage_totals": {
- "dynamic": "false",
- "type": "object"
- },
- "application_usage_transactional": {
- "dynamic": "false",
- "type": "object"
- },
- "canvas-element": {
- "dynamic": "false",
- "properties": {
- "@created": {
- "type": "date"
- },
- "@timestamp": {
- "type": "date"
- },
- "content": {
- "type": "text"
- },
- "help": {
- "type": "text"
- },
- "image": {
- "type": "text"
- },
- "name": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "canvas-workpad": {
- "dynamic": "false",
- "properties": {
- "@created": {
- "type": "date"
- },
- "@timestamp": {
- "type": "date"
- },
- "name": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "canvas-workpad-template": {
- "dynamic": "false",
- "properties": {
- "help": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "name": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "tags": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "template_key": {
- "type": "keyword"
- }
- }
- },
- "cases": {
- "properties": {
- "closed_at": {
- "type": "date"
- },
- "closed_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- },
- "connector_id": {
- "type": "keyword"
- },
- "created_at": {
- "type": "date"
- },
- "created_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- },
- "description": {
- "type": "text"
- },
- "external_service": {
- "properties": {
- "connector_id": {
- "type": "keyword"
- },
- "connector_name": {
- "type": "keyword"
- },
- "external_id": {
- "type": "keyword"
- },
- "external_title": {
- "type": "text"
- },
- "external_url": {
- "type": "text"
- },
- "pushed_at": {
- "type": "date"
- },
- "pushed_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- }
- }
- },
- "status": {
- "type": "keyword"
- },
- "tags": {
- "type": "keyword"
- },
- "title": {
- "type": "keyword"
- },
- "updated_at": {
- "type": "date"
- },
- "updated_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- }
- }
- },
- "cases-comments": {
- "properties": {
- "comment": {
- "type": "text"
- },
- "created_at": {
- "type": "date"
- },
- "created_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- },
- "pushed_at": {
- "type": "date"
- },
- "pushed_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- },
- "updated_at": {
- "type": "date"
- },
- "updated_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- }
- }
- },
- "cases-configure": {
- "properties": {
- "closure_type": {
- "type": "keyword"
- },
- "connector_id": {
- "type": "keyword"
- },
- "connector_name": {
- "type": "keyword"
- },
- "created_at": {
- "type": "date"
- },
- "created_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- },
- "updated_at": {
- "type": "date"
- },
- "updated_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- }
- }
- },
- "cases-user-actions": {
- "properties": {
- "action": {
- "type": "keyword"
- },
- "action_at": {
- "type": "date"
- },
- "action_by": {
- "properties": {
- "email": {
- "type": "keyword"
- },
- "full_name": {
- "type": "keyword"
- },
- "username": {
- "type": "keyword"
- }
- }
- },
- "action_field": {
- "type": "keyword"
- },
- "new_value": {
- "type": "text"
- },
- "old_value": {
- "type": "text"
- }
- }
- },
- "config": {
- "dynamic": "false",
- "properties": {
- "buildNum": {
- "type": "keyword"
- }
- }
- },
- "dashboard": {
- "properties": {
- "description": {
- "type": "text"
- },
- "hits": {
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "optionsJSON": {
- "type": "text"
- },
- "panelsJSON": {
- "type": "text"
- },
- "refreshInterval": {
- "properties": {
- "display": {
- "type": "keyword"
- },
- "pause": {
- "type": "boolean"
- },
- "section": {
- "type": "integer"
- },
- "value": {
- "type": "integer"
- }
- }
- },
- "timeFrom": {
- "type": "keyword"
- },
- "timeRestore": {
- "type": "boolean"
- },
- "timeTo": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "endpoint:user-artifact": {
- "properties": {
- "body": {
- "type": "binary"
- },
- "compressionAlgorithm": {
- "index": false,
- "type": "keyword"
- },
- "created": {
- "index": false,
- "type": "date"
- },
- "decodedSha256": {
- "index": false,
- "type": "keyword"
- },
- "decodedSize": {
- "index": false,
- "type": "long"
- },
- "encodedSha256": {
- "type": "keyword"
- },
- "encodedSize": {
- "index": false,
- "type": "long"
- },
- "encryptionAlgorithm": {
- "index": false,
- "type": "keyword"
- },
- "identifier": {
- "type": "keyword"
- }
- }
- },
- "endpoint:user-artifact-manifest": {
- "properties": {
- "created": {
- "index": false,
- "type": "date"
- },
- "schemaVersion": {
- "type": "keyword"
- },
- "semanticVersion": {
- "index": false,
- "type": "keyword"
- },
- "artifacts": {
- "type": "nested",
- "properties": {
- "policyId": {
- "type": "keyword",
- "index": false
- },
- "artifactId": {
- "type": "keyword",
- "index": false
- }
- }
- }
- }
- },
- "epm-packages": {
- "properties": {
- "es_index_patterns": {
- "enabled": false,
- "type": "object"
- },
- "installed_es": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "installed_kibana": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "internal": {
- "type": "boolean"
- },
- "name": {
- "type": "keyword"
- },
- "removable": {
- "type": "boolean"
- },
- "version": {
- "type": "keyword"
- }
- }
- },
- "exception-list": {
- "properties": {
- "_tags": {
- "type": "keyword"
- },
- "comments": {
- "properties": {
- "comment": {
- "type": "keyword"
- },
- "created_at": {
- "type": "keyword"
- },
- "created_by": {
- "type": "keyword"
- },
- "id": {
- "type": "keyword"
- },
- "updated_at": {
- "type": "keyword"
- },
- "updated_by": {
- "type": "keyword"
- }
- }
- },
- "created_at": {
- "type": "keyword"
- },
- "created_by": {
- "type": "keyword"
- },
- "description": {
- "type": "keyword"
- },
- "entries": {
- "properties": {
- "entries": {
- "properties": {
- "field": {
- "type": "keyword"
- },
- "operator": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "value": {
- "fields": {
- "text": {
- "type": "text"
- }
- },
- "type": "keyword"
- }
- }
- },
- "field": {
- "type": "keyword"
- },
- "list": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "operator": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "value": {
- "fields": {
- "text": {
- "type": "text"
- }
- },
- "type": "keyword"
- }
- }
- },
- "immutable": {
- "type": "boolean"
- },
- "item_id": {
- "type": "keyword"
- },
- "list_id": {
- "type": "keyword"
- },
- "list_type": {
- "type": "keyword"
- },
- "meta": {
- "type": "keyword"
- },
- "name": {
- "type": "keyword"
- },
- "tags": {
- "type": "keyword"
- },
- "tie_breaker_id": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "updated_by": {
- "type": "keyword"
- },
- "version": {
- "type": "keyword"
- }
- }
- },
- "exception-list-agnostic": {
- "properties": {
- "_tags": {
- "type": "keyword"
- },
- "comments": {
- "properties": {
- "comment": {
- "type": "keyword"
- },
- "created_at": {
- "type": "keyword"
- },
- "created_by": {
- "type": "keyword"
- },
- "id": {
- "type": "keyword"
- },
- "updated_at": {
- "type": "keyword"
- },
- "updated_by": {
- "type": "keyword"
- }
- }
- },
- "created_at": {
- "type": "keyword"
- },
- "created_by": {
- "type": "keyword"
- },
- "description": {
- "type": "keyword"
- },
- "entries": {
- "properties": {
- "entries": {
- "properties": {
- "field": {
- "type": "keyword"
- },
- "operator": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "value": {
- "fields": {
- "text": {
- "type": "text"
- }
- },
- "type": "keyword"
- }
- }
- },
- "field": {
- "type": "keyword"
- },
- "list": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "operator": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "value": {
- "fields": {
- "text": {
- "type": "text"
- }
- },
- "type": "keyword"
- }
- }
- },
- "immutable": {
- "type": "boolean"
- },
- "item_id": {
- "type": "keyword"
- },
- "list_id": {
- "type": "keyword"
- },
- "list_type": {
- "type": "keyword"
- },
- "meta": {
- "type": "keyword"
- },
- "name": {
- "type": "keyword"
- },
- "tags": {
- "type": "keyword"
- },
- "tie_breaker_id": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "updated_by": {
- "type": "keyword"
- },
- "version": {
- "type": "keyword"
- }
- }
- },
- "file-upload-telemetry": {
- "properties": {
- "filesUploadedTotalCount": {
- "type": "long"
- }
- }
- },
- "fleet-agent-actions": {
- "properties": {
- "agent_id": {
- "type": "keyword"
- },
- "created_at": {
- "type": "date"
- },
- "data": {
- "type": "binary"
- },
- "sent_at": {
- "type": "date"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "fleet-agent-events": {
- "properties": {
- "action_id": {
- "type": "keyword"
- },
- "agent_id": {
- "type": "keyword"
- },
- "config_id": {
- "type": "keyword"
- },
- "data": {
- "type": "text"
- },
- "message": {
- "type": "text"
- },
- "payload": {
- "type": "text"
- },
- "stream_id": {
- "type": "keyword"
- },
- "subtype": {
- "type": "keyword"
- },
- "timestamp": {
- "type": "date"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "fleet-agents": {
- "properties": {
- "access_api_key_id": {
- "type": "keyword"
- },
- "active": {
- "type": "boolean"
- },
- "config_id": {
- "type": "keyword"
- },
- "config_revision": {
- "type": "integer"
- },
- "current_error_events": {
- "index": false,
- "type": "text"
- },
- "default_api_key": {
- "type": "binary"
- },
- "default_api_key_id": {
- "type": "keyword"
- },
- "enrolled_at": {
- "type": "date"
- },
- "last_checkin": {
- "type": "date"
- },
- "last_checkin_status": {
- "type": "keyword"
- },
- "last_updated": {
- "type": "date"
- },
- "local_metadata": {
- "type": "flattened"
- },
- "packages": {
- "type": "keyword"
- },
- "shared_id": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "unenrolled_at": {
- "type": "date"
- },
- "unenrollment_started_at": {
- "type": "date"
- },
- "updated_at": {
- "type": "date"
- },
- "user_provided_metadata": {
- "type": "flattened"
- },
- "version": {
- "type": "keyword"
- }
- }
- },
- "fleet-enrollment-api-keys": {
- "properties": {
- "active": {
- "type": "boolean"
- },
- "api_key": {
- "type": "binary"
- },
- "api_key_id": {
- "type": "keyword"
- },
- "config_id": {
- "type": "keyword"
- },
- "created_at": {
- "type": "date"
- },
- "expire_at": {
- "type": "date"
- },
- "name": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "updated_at": {
- "type": "date"
- }
- }
- },
- "graph-workspace": {
- "properties": {
- "description": {
- "type": "text"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "numLinks": {
- "type": "integer"
- },
- "numVertices": {
- "type": "integer"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- },
- "wsState": {
- "type": "text"
- }
- }
- },
- "index-pattern": {
- "properties": {
- "fieldFormatMap": {
- "type": "text"
- },
- "fields": {
- "type": "text"
- },
- "intervalName": {
- "type": "keyword"
- },
- "notExpandable": {
- "type": "boolean"
- },
- "sourceFilters": {
- "type": "text"
- },
- "timeFieldName": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "type": {
- "type": "keyword"
- },
- "typeMeta": {
- "type": "keyword"
- }
- }
- },
- "infrastructure-ui-source": {
- "properties": {
- "description": {
- "type": "text"
- },
- "fields": {
- "properties": {
- "container": {
- "type": "keyword"
- },
- "host": {
- "type": "keyword"
- },
- "pod": {
- "type": "keyword"
- },
- "tiebreaker": {
- "type": "keyword"
- },
- "timestamp": {
- "type": "keyword"
- }
- }
- },
- "inventoryDefaultView": {
- "type": "keyword"
- },
- "logAlias": {
- "type": "keyword"
- },
- "logColumns": {
- "properties": {
- "fieldColumn": {
- "properties": {
- "field": {
- "type": "keyword"
- },
- "id": {
- "type": "keyword"
- }
- }
- },
- "messageColumn": {
- "properties": {
- "id": {
- "type": "keyword"
- }
- }
- },
- "timestampColumn": {
- "properties": {
- "id": {
- "type": "keyword"
- }
- }
- }
- },
- "type": "nested"
- },
- "metricAlias": {
- "type": "keyword"
- },
- "metricsExplorerDefaultView": {
- "type": "keyword"
- },
- "name": {
- "type": "text"
- }
- }
- },
- "ingest-agent-policies": {
- "properties": {
- "description": {
- "type": "text"
- },
- "is_default": {
- "type": "boolean"
- },
- "monitoring_enabled": {
- "index": false,
- "type": "keyword"
- },
- "name": {
- "type": "keyword"
- },
- "namespace": {
- "type": "keyword"
- },
- "package_configs": {
- "type": "keyword"
- },
- "revision": {
- "type": "integer"
- },
- "status": {
- "type": "keyword"
- },
- "updated_at": {
- "type": "date"
- },
- "updated_by": {
- "type": "keyword"
- }
- }
- },
- "ingest-outputs": {
- "properties": {
- "ca_sha256": {
- "index": false,
- "type": "keyword"
- },
- "config": {
- "type": "flattened"
- },
- "fleet_enroll_password": {
- "type": "binary"
- },
- "fleet_enroll_username": {
- "type": "binary"
- },
- "hosts": {
- "type": "keyword"
- },
- "is_default": {
- "type": "boolean"
- },
- "name": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "ingest-package-policies": {
- "properties": {
- "config_id": {
- "type": "keyword"
- },
- "created_at": {
- "type": "date"
- },
- "created_by": {
- "type": "keyword"
- },
- "description": {
- "type": "text"
- },
- "enabled": {
- "type": "boolean"
- },
- "inputs": {
- "enabled": false,
- "properties": {
- "config": {
- "type": "flattened"
- },
- "enabled": {
- "type": "boolean"
- },
- "streams": {
- "properties": {
- "compiled_stream": {
- "type": "flattened"
- },
- "config": {
- "type": "flattened"
- },
- "data_stream": {
- "properties": {
- "dataset": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "enabled": {
- "type": "boolean"
- },
- "id": {
- "type": "keyword"
- },
- "vars": {
- "type": "flattened"
- }
- },
- "type": "nested"
- },
- "type": {
- "type": "keyword"
- },
- "vars": {
- "type": "flattened"
- }
- },
- "type": "nested"
- },
- "name": {
- "type": "keyword"
- },
- "namespace": {
- "type": "keyword"
- },
- "output_id": {
- "type": "keyword"
- },
- "package": {
- "properties": {
- "name": {
- "type": "keyword"
- },
- "title": {
- "type": "keyword"
- },
- "version": {
- "type": "keyword"
- }
- }
- },
- "revision": {
- "type": "integer"
- },
- "updated_at": {
- "type": "date"
- },
- "updated_by": {
- "type": "keyword"
- }
- }
- },
- "ingest_manager_settings": {
- "properties": {
- "agent_auto_upgrade": {
- "type": "keyword"
- },
- "has_seen_add_data_notice": {
- "index": false,
- "type": "boolean"
- },
- "kibana_ca_sha256": {
- "type": "keyword"
- },
- "kibana_url": {
- "type": "keyword"
- },
- "package_auto_upgrade": {
- "type": "keyword"
- }
- }
- },
- "inventory-view": {
- "properties": {
- "accountId": {
- "type": "keyword"
- },
- "autoBounds": {
- "type": "boolean"
- },
- "autoReload": {
- "type": "boolean"
- },
- "boundsOverride": {
- "properties": {
- "max": {
- "type": "integer"
- },
- "min": {
- "type": "integer"
- }
- }
- },
- "customMetrics": {
- "properties": {
- "aggregation": {
- "type": "keyword"
- },
- "field": {
- "type": "keyword"
- },
- "id": {
- "type": "keyword"
- },
- "label": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "customOptions": {
- "properties": {
- "field": {
- "type": "keyword"
- },
- "text": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "filterQuery": {
- "properties": {
- "expression": {
- "type": "keyword"
- },
- "kind": {
- "type": "keyword"
- }
- }
- },
- "groupBy": {
- "properties": {
- "field": {
- "type": "keyword"
- },
- "label": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "legend": {
- "properties": {
- "palette": {
- "type": "keyword"
- },
- "reverseColors": {
- "type": "boolean"
- },
- "steps": {
- "type": "long"
- }
- }
- },
- "metric": {
- "properties": {
- "aggregation": {
- "type": "keyword"
- },
- "field": {
- "type": "keyword"
- },
- "id": {
- "type": "keyword"
- },
- "label": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "name": {
- "type": "keyword"
- },
- "nodeType": {
- "type": "keyword"
- },
- "region": {
- "type": "keyword"
- },
- "sort": {
- "properties": {
- "by": {
- "type": "keyword"
- },
- "direction": {
- "type": "keyword"
- }
- }
- },
- "time": {
- "type": "long"
- },
- "view": {
- "type": "keyword"
- }
- }
- },
- "kql-telemetry": {
- "properties": {
- "optInCount": {
- "type": "long"
- },
- "optOutCount": {
- "type": "long"
- }
- }
- },
- "lens": {
- "properties": {
- "description": {
- "type": "text"
- },
- "expression": {
- "index": false,
- "type": "keyword"
- },
- "state": {
- "type": "flattened"
- },
- "title": {
- "type": "text"
- },
- "visualizationType": {
- "type": "keyword"
- }
- }
- },
- "lens-ui-telemetry": {
- "properties": {
- "count": {
- "type": "integer"
- },
- "date": {
- "type": "date"
- },
- "name": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "map": {
- "properties": {
- "description": {
- "type": "text"
- },
- "layerListJSON": {
- "type": "text"
- },
- "mapStateJSON": {
- "type": "text"
- },
- "title": {
- "type": "text"
- },
- "uiStateJSON": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "maps-telemetry": {
- "enabled": false,
- "type": "object"
- },
- "metrics-explorer-view": {
- "properties": {
- "chartOptions": {
- "properties": {
- "stack": {
- "type": "boolean"
- },
- "type": {
- "type": "keyword"
- },
- "yAxisMode": {
- "type": "keyword"
- }
- }
- },
- "currentTimerange": {
- "properties": {
- "from": {
- "type": "keyword"
- },
- "interval": {
- "type": "keyword"
- },
- "to": {
- "type": "keyword"
- }
- }
- },
- "name": {
- "type": "keyword"
- },
- "options": {
- "properties": {
- "aggregation": {
- "type": "keyword"
- },
- "filterQuery": {
- "type": "keyword"
- },
- "forceInterval": {
- "type": "boolean"
- },
- "groupBy": {
- "type": "keyword"
- },
- "limit": {
- "type": "integer"
- },
- "metrics": {
- "properties": {
- "aggregation": {
- "type": "keyword"
- },
- "color": {
- "type": "keyword"
- },
- "field": {
- "type": "keyword"
- },
- "label": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "source": {
- "type": "keyword"
- }
- }
- }
- }
- },
- "migrationVersion": {
- "dynamic": "true",
- "properties": {
- "config": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "space": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "ml-telemetry": {
- "properties": {
- "file_data_visualizer": {
- "properties": {
- "index_creation_count": {
- "type": "long"
- }
- }
- }
- }
- },
- "namespace": {
- "type": "keyword"
- },
- "namespaces": {
- "type": "keyword"
- },
- "query": {
- "properties": {
- "description": {
- "type": "text"
- },
- "filters": {
- "enabled": false,
- "type": "object"
- },
- "query": {
- "properties": {
- "language": {
- "type": "keyword"
- },
- "query": {
- "index": false,
- "type": "keyword"
- }
- }
- },
- "timefilter": {
- "enabled": false,
- "type": "object"
- },
- "title": {
- "type": "text"
- }
- }
- },
- "references": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "name": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- }
- },
- "type": "nested"
- },
- "sample-data-telemetry": {
- "properties": {
- "installCount": {
- "type": "long"
- },
- "unInstallCount": {
- "type": "long"
- }
- }
- },
- "search": {
- "properties": {
- "columns": {
- "index": false,
- "type": "keyword"
- },
- "description": {
- "type": "text"
- },
- "hits": {
- "index": false,
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "index": false,
- "type": "text"
- }
- }
- },
- "sort": {
- "index": false,
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "search-telemetry": {
- "dynamic": "false",
- "type": "object"
- },
- "siem-detection-engine-rule-actions": {
- "properties": {
- "actions": {
- "properties": {
- "action_type_id": {
- "type": "keyword"
- },
- "group": {
- "type": "keyword"
- },
- "id": {
- "type": "keyword"
- },
- "params": {
- "enabled": false,
- "type": "object"
- }
- }
- },
- "alertThrottle": {
- "type": "keyword"
- },
- "ruleAlertId": {
- "type": "keyword"
- },
- "ruleThrottle": {
- "type": "keyword"
- }
- }
- },
- "siem-detection-engine-rule-status": {
- "properties": {
- "alertId": {
- "type": "keyword"
- },
- "bulkCreateTimeDurations": {
- "type": "float"
- },
- "gap": {
- "type": "text"
- },
- "lastFailureAt": {
- "type": "date"
- },
- "lastFailureMessage": {
- "type": "text"
- },
- "lastLookBackDate": {
- "type": "date"
- },
- "lastSuccessAt": {
- "type": "date"
- },
- "lastSuccessMessage": {
- "type": "text"
- },
- "searchAfterTimeDurations": {
- "type": "float"
- },
- "status": {
- "type": "keyword"
- },
- "statusDate": {
- "type": "date"
- }
- }
- },
- "siem-ui-timeline": {
- "properties": {
- "columns": {
- "properties": {
- "aggregatable": {
- "type": "boolean"
- },
- "category": {
- "type": "keyword"
- },
- "columnHeaderType": {
- "type": "keyword"
- },
- "description": {
- "type": "text"
- },
- "example": {
- "type": "text"
- },
- "id": {
- "type": "keyword"
- },
- "indexes": {
- "type": "keyword"
- },
- "name": {
- "type": "text"
- },
- "placeholder": {
- "type": "text"
- },
- "searchable": {
- "type": "boolean"
- },
- "type": {
- "type": "keyword"
- }
- }
- },
- "created": {
- "type": "date"
- },
- "createdBy": {
- "type": "text"
- },
- "dataProviders": {
- "properties": {
- "and": {
- "properties": {
- "enabled": {
- "type": "boolean"
- },
- "excluded": {
- "type": "boolean"
- },
- "id": {
- "type": "keyword"
- },
- "kqlQuery": {
- "type": "text"
- },
- "name": {
- "type": "text"
- },
- "queryMatch": {
- "properties": {
- "displayField": {
- "type": "text"
- },
- "displayValue": {
- "type": "text"
- },
- "field": {
- "type": "text"
- },
- "operator": {
- "type": "text"
- },
- "value": {
- "type": "text"
- }
- }
- },
- "type": {
- "type": "text"
- }
- }
- },
- "enabled": {
- "type": "boolean"
- },
- "excluded": {
- "type": "boolean"
- },
- "id": {
- "type": "keyword"
- },
- "kqlQuery": {
- "type": "text"
- },
- "name": {
- "type": "text"
- },
- "queryMatch": {
- "properties": {
- "displayField": {
- "type": "text"
- },
- "displayValue": {
- "type": "text"
- },
- "field": {
- "type": "text"
- },
- "operator": {
- "type": "text"
- },
- "value": {
- "type": "text"
- }
- }
- },
- "type": {
- "type": "text"
- }
- }
- },
- "dateRange": {
- "properties": {
- "end": {
- "type": "date"
- },
- "start": {
- "type": "date"
- }
- }
- },
- "description": {
- "type": "text"
- },
- "eventType": {
- "type": "keyword"
- },
- "excludedRowRendererIds": {
- "type": "text"
- },
- "favorite": {
- "properties": {
- "favoriteDate": {
- "type": "date"
- },
- "fullName": {
- "type": "text"
- },
- "keySearch": {
- "type": "text"
- },
- "userName": {
- "type": "text"
- }
- }
- },
- "filters": {
- "properties": {
- "exists": {
- "type": "text"
- },
- "match_all": {
- "type": "text"
- },
- "meta": {
- "properties": {
- "alias": {
- "type": "text"
- },
- "controlledBy": {
- "type": "text"
- },
- "disabled": {
- "type": "boolean"
- },
- "field": {
- "type": "text"
- },
- "formattedValue": {
- "type": "text"
- },
- "index": {
- "type": "keyword"
- },
- "key": {
- "type": "keyword"
- },
- "negate": {
- "type": "boolean"
- },
- "params": {
- "type": "text"
- },
- "type": {
- "type": "keyword"
- },
- "value": {
- "type": "text"
- }
- }
- },
- "missing": {
- "type": "text"
- },
- "query": {
- "type": "text"
- },
- "range": {
- "type": "text"
- },
- "script": {
- "type": "text"
- }
- }
- },
- "kqlMode": {
- "type": "keyword"
- },
- "kqlQuery": {
- "properties": {
- "filterQuery": {
- "properties": {
- "kuery": {
- "properties": {
- "expression": {
- "type": "text"
- },
- "kind": {
- "type": "keyword"
- }
- }
- },
- "serializedQuery": {
- "type": "text"
- }
- }
- }
- }
- },
- "savedQueryId": {
- "type": "keyword"
- },
- "sort": {
- "properties": {
- "columnId": {
- "type": "keyword"
- },
- "sortDirection": {
- "type": "keyword"
- }
- }
- },
- "status": {
- "type": "keyword"
- },
- "templateTimelineId": {
- "type": "text"
- },
- "templateTimelineVersion": {
- "type": "integer"
- },
- "timelineType": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "updated": {
- "type": "date"
- },
- "updatedBy": {
- "type": "text"
- }
- }
- },
- "siem-ui-timeline-note": {
- "properties": {
- "created": {
- "type": "date"
- },
- "createdBy": {
- "type": "text"
- },
- "eventId": {
- "type": "keyword"
- },
- "note": {
- "type": "text"
- },
- "timelineId": {
- "type": "keyword"
- },
- "updated": {
- "type": "date"
- },
- "updatedBy": {
- "type": "text"
- }
- }
- },
- "siem-ui-timeline-pinned-event": {
- "properties": {
- "created": {
- "type": "date"
- },
- "createdBy": {
- "type": "text"
- },
- "eventId": {
- "type": "keyword"
- },
- "timelineId": {
- "type": "keyword"
- },
- "updated": {
- "type": "date"
- },
- "updatedBy": {
- "type": "text"
- }
- }
- },
- "space": {
- "properties": {
- "_reserved": {
- "type": "boolean"
- },
- "color": {
- "type": "keyword"
- },
- "description": {
- "type": "text"
- },
- "disabledFeatures": {
- "type": "keyword"
- },
- "imageUrl": {
- "index": false,
- "type": "text"
- },
- "initials": {
- "type": "keyword"
- },
- "name": {
- "fields": {
- "keyword": {
- "ignore_above": 2048,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "telemetry": {
- "properties": {
- "allowChangingOptInStatus": {
- "type": "boolean"
- },
- "enabled": {
- "type": "boolean"
- },
- "lastReported": {
- "type": "date"
- },
- "lastVersionChecked": {
- "type": "keyword"
- },
- "reportFailureCount": {
- "type": "integer"
- },
- "reportFailureVersion": {
- "type": "keyword"
- },
- "sendUsageFrom": {
- "type": "keyword"
- },
- "userHasSeenNotice": {
- "type": "boolean"
- }
- }
- },
- "tsvb-validation-telemetry": {
- "properties": {
- "failedRequests": {
- "type": "long"
- }
- }
- },
- "type": {
- "type": "keyword"
- },
- "ui-metric": {
- "properties": {
- "count": {
- "type": "integer"
- }
- }
- },
- "updated_at": {
- "type": "date"
- },
- "upgrade-assistant-reindex-operation": {
- "properties": {
- "errorMessage": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "indexName": {
- "type": "keyword"
- },
- "lastCompletedStep": {
- "type": "long"
- },
- "locked": {
- "type": "date"
- },
- "newIndexName": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "reindexOptions": {
- "properties": {
- "openAndClose": {
- "type": "boolean"
- },
- "queueSettings": {
- "properties": {
- "queuedAt": {
- "type": "long"
- },
- "startedAt": {
- "type": "long"
- }
- }
- }
- }
- },
- "reindexTaskId": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "reindexTaskPercComplete": {
- "type": "float"
- },
- "runningReindexCount": {
- "type": "integer"
- },
- "status": {
- "type": "integer"
- }
- }
- },
- "upgrade-assistant-telemetry": {
- "properties": {
- "features": {
- "properties": {
- "deprecation_logging": {
- "properties": {
- "enabled": {
- "null_value": true,
- "type": "boolean"
- }
- }
- }
- }
- },
- "ui_open": {
- "properties": {
- "cluster": {
- "null_value": 0,
- "type": "long"
- },
- "indices": {
- "null_value": 0,
- "type": "long"
- },
- "overview": {
- "null_value": 0,
- "type": "long"
- }
- }
- },
- "ui_reindex": {
- "properties": {
- "close": {
- "null_value": 0,
- "type": "long"
- },
- "open": {
- "null_value": 0,
- "type": "long"
- },
- "start": {
- "null_value": 0,
- "type": "long"
- },
- "stop": {
- "null_value": 0,
- "type": "long"
- }
- }
- }
- }
- },
- "uptime-dynamic-settings": {
- "properties": {
- "certAgeThreshold": {
- "type": "long"
- },
- "certExpirationThreshold": {
- "type": "long"
- },
- "heartbeatIndices": {
- "type": "keyword"
- }
- }
- },
- "url": {
- "properties": {
- "accessCount": {
- "type": "long"
- },
- "accessDate": {
- "type": "date"
- },
- "createDate": {
- "type": "date"
- },
- "url": {
- "fields": {
- "keyword": {
- "ignore_above": 2048,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "visualization": {
- "properties": {
- "description": {
- "type": "text"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "savedSearchRefName": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "uiStateJSON": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- },
- "visState": {
- "type": "text"
- }
- }
- },
- "workplace_search_telemetry": {
- "dynamic": "false",
- "type": "object"
- }
- }
- },
- "settings": {
- "index": {
- "auto_expand_replicas": "0-1",
- "number_of_replicas": "0",
- "number_of_shards": "1"
- }
- }
- }
-}
From 4397787fb19e27ef42cbd7636e29cd0e2d89fd2d Mon Sep 17 00:00:00 2001
From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com>
Date: Fri, 31 May 2024 16:21:12 +0200
Subject: [PATCH 09/46] [ES|QL] Do not load fields on every keystroke, when
editing just the source command (#184423)
## Summary
Fixes https://github.com/elastic/kibana/issues/184093
Closes https://github.com/elastic/kibana/pull/184417
- Fixes the problem on language tools layer.
- Prevents fields loading on every keystroke, when there is only a
single source command, which does not need the fields (FROM, SHOW, ROW).
It does it in two places:
- Validation
- Autocompletion
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or ### For maintainers
- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../src/__tests__/helpers.ts | 73 +++++++++++++++++++
.../autocomplete/autocomplete.suggest.test.ts | 49 +++++++++++++
.../src/autocomplete/autocomplete.ts | 3 +-
.../src/autocomplete/helper.ts | 8 ++
.../src/shared/resources_helpers.ts | 2 +-
.../src/validation/resources.ts | 11 +++
.../src/validation/validation.from.test.ts | 49 +++++++++++++
.../src/validation/validation.test.ts | 67 ++---------------
8 files changed, 201 insertions(+), 61 deletions(-)
create mode 100644 packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts
create mode 100644 packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.suggest.test.ts
create mode 100644 packages/kbn-esql-validation-autocomplete/src/validation/validation.from.test.ts
diff --git a/packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts
new file mode 100644
index 0000000000000..f3c159247e260
--- /dev/null
+++ b/packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { camelCase } from 'lodash';
+import { supportedFieldTypes } from '../definitions/types';
+
+export const fields = [
+ ...supportedFieldTypes.map((type) => ({ name: `${camelCase(type)}Field`, type })),
+ { name: 'any#Char$Field', type: 'number' },
+ { name: 'kubernetes.something.something', type: 'number' },
+ { name: '@timestamp', type: 'date' },
+];
+
+export const enrichFields = [
+ { name: 'otherField', type: 'string' },
+ { name: 'yetAnotherField', type: 'number' },
+];
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export const unsupported_field = [{ name: 'unsupported_field', type: 'unsupported' }];
+
+export const indexes = [
+ 'a_index',
+ 'index',
+ 'other_index',
+ '.secret_index',
+ 'my-index',
+ 'unsupported_index',
+];
+
+export const policies = [
+ {
+ name: 'policy',
+ sourceIndices: ['enrich_index'],
+ matchField: 'otherStringField',
+ enrichFields: ['otherField', 'yetAnotherField'],
+ },
+ {
+ name: 'policy$',
+ sourceIndices: ['enrich_index'],
+ matchField: 'otherStringField',
+ enrichFields: ['otherField', 'yetAnotherField'],
+ },
+];
+
+export function getCallbackMocks() {
+ return {
+ getFieldsFor: jest.fn(async ({ query }) => {
+ if (/enrich/.test(query)) {
+ return enrichFields;
+ }
+ if (/unsupported_index/.test(query)) {
+ return unsupported_field;
+ }
+ if (/dissect|grok/.test(query)) {
+ return [{ name: 'firstWord', type: 'string' }];
+ }
+ return fields;
+ }),
+ getSources: jest.fn(async () =>
+ indexes.map((name) => ({
+ name,
+ hidden: name.startsWith('.'),
+ }))
+ ),
+ getPolicies: jest.fn(async () => policies),
+ };
+}
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.suggest.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.suggest.test.ts
new file mode 100644
index 0000000000000..7bb96f602c0dc
--- /dev/null
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.suggest.test.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { getAstAndSyntaxErrors } from '@kbn/esql-ast';
+import { ESQLCallbacks } from '../shared/types';
+import * as autocomplete from './autocomplete';
+import { getCallbackMocks } from '../__tests__/helpers';
+import { EditorContext } from './types';
+
+const setup = async (caret = '?') => {
+ if (caret.length !== 1) throw new Error('Caret must be a single character');
+ const callbacks = getCallbackMocks();
+ const suggest = async (
+ query: string,
+ ctx: EditorContext = {
+ triggerKind: 0,
+ },
+ cb: ESQLCallbacks = callbacks
+ ) => {
+ const pos = query.indexOf(caret);
+ if (pos < 0) throw new Error(`User cursor/caret "${caret}" not found in query: ${query}`);
+ const querySansCaret = query.slice(0, pos) + query.slice(pos + 1);
+ return await autocomplete.suggest(querySansCaret, pos, ctx, getAstAndSyntaxErrors, cb);
+ };
+
+ return {
+ callbacks,
+ suggest,
+ };
+};
+
+describe('autocomplete.suggest', () => {
+ test('does not load fields when suggesting within a single FROM, SHOW, ROW command', async () => {
+ const { suggest, callbacks } = await setup();
+
+ await suggest('FROM kib, ? |');
+ await suggest('FROM ?');
+ await suggest('FROM ? |');
+ await suggest('sHoW ?');
+ await suggest('row ? |');
+
+ expect(callbacks.getFieldsFor.mock.calls.length).toBe(0);
+ });
+});
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
index 2d0c2cd9e757e..e753255624383 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
@@ -83,6 +83,7 @@ import { ESQLCallbacks } from '../shared/types';
import {
getFunctionsToIgnoreForStats,
getParamAtPosition,
+ getQueryForFields,
isAggFunctionUsedAlready,
} from './helper';
import { FunctionArgSignature } from '../definitions/types';
@@ -196,7 +197,7 @@ export async function suggest(
const astContext = getAstContext(innerText, ast, offset);
// build the correct query to fetch the list of fields
- const queryForFields = buildQueryUntilPreviousCommand(ast, finalText);
+ const queryForFields = getQueryForFields(buildQueryUntilPreviousCommand(ast, finalText), ast);
const { getFieldsByType, getFieldsMap } = getFieldsByTypeRetriever(
queryForFields,
resourceRetriever
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts
index c52c18b822068..7bba3a5ab15ec 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts
@@ -55,3 +55,11 @@ export function getParamAtPosition(
) {
return params.length > position ? params[position] : minParams ? params[params.length - 1] : null;
}
+
+export function getQueryForFields(queryString: string, commands: ESQLCommand[]) {
+ // If there is only one source command and it does not require fields, do not
+ // fetch fields, hence return an empty string.
+ return commands.length === 1 && ['from', 'row', 'show'].includes(commands[0].name)
+ ? ''
+ : queryString;
+}
diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/resources_helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/resources_helpers.ts
index e44d874f8674c..8433b95e99b39 100644
--- a/packages/kbn-esql-validation-autocomplete/src/shared/resources_helpers.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/shared/resources_helpers.ts
@@ -18,7 +18,7 @@ export function buildQueryUntilPreviousCommand(ast: ESQLAst, queryString: string
export function getFieldsByTypeHelper(queryText: string, resourceRetriever?: ESQLCallbacks) {
const cacheFields = new Map();
const getFields = async () => {
- if (!cacheFields.size) {
+ if (!cacheFields.size && queryText) {
const fieldsOfType = await resourceRetriever?.getFieldsFor?.({ query: queryText });
for (const field of fieldsOfType || []) {
cacheFields.set(field.name, field);
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/resources.ts b/packages/kbn-esql-validation-autocomplete/src/validation/resources.ts
index 7ec5ff7fc0a2b..d66bee1de1e7e 100644
--- a/packages/kbn-esql-validation-autocomplete/src/validation/resources.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/resources.ts
@@ -29,6 +29,17 @@ export async function retrieveFields(
if (!callbacks || commands.length < 1) {
return new Map();
}
+ // Do not fetch fields, if query has only one source command and that command
+ // does not require fields.
+ if (commands.length === 1) {
+ switch (commands[0].name) {
+ case 'from':
+ case 'show':
+ case 'row': {
+ return new Map();
+ }
+ }
+ }
if (commands[0].name === 'row') {
return new Map();
}
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.from.test.ts
new file mode 100644
index 0000000000000..1639800c446d8
--- /dev/null
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.from.test.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { getAstAndSyntaxErrors } from '@kbn/esql-ast';
+import { ESQLCallbacks } from '../shared/types';
+import { ValidationOptions } from './types';
+import { validateQuery } from './validation';
+import { getCallbackMocks } from '../__tests__/helpers';
+
+const setup = async () => {
+ const callbacks = getCallbackMocks();
+ const validate = async (
+ query: string,
+ opts: ValidationOptions = {},
+ cb: ESQLCallbacks = callbacks
+ ) => {
+ return await validateQuery(query, getAstAndSyntaxErrors, opts, cb);
+ };
+
+ return {
+ callbacks,
+ validate,
+ };
+};
+
+test('does not load fields when validating only a single FROM, SHOW, ROW command', async () => {
+ const { validate, callbacks } = await setup();
+
+ await validate('FROM kib');
+ await validate('FROM kibana_ecommerce METADATA _i');
+ await validate('FROM kibana_ecommerce METADATA _id | ');
+ await validate('SHOW');
+ await validate('ROW \t');
+
+ expect(callbacks.getFieldsFor.mock.calls.length).toBe(0);
+});
+
+test('loads fields with FROM source when commands after pipe present', async () => {
+ const { validate, callbacks } = await setup();
+
+ await validate('FROM kibana_ecommerce METADATA _id | eval');
+
+ expect(callbacks.getFieldsFor.mock.calls.length).toBe(1);
+});
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
index 42879128cf663..f0c150ee47a1c 100644
--- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
@@ -20,71 +20,20 @@ import { getAstAndSyntaxErrors } from '@kbn/esql-ast';
import { nonNullable } from '../shared/helpers';
import { METADATA_FIELDS } from '../shared/constants';
import { FUNCTION_DESCRIBE_BLOCK_NAME } from './function_describe_block_name';
-
-const fields = [
- ...supportedFieldTypes.map((type) => ({ name: `${camelCase(type)}Field`, type })),
- { name: 'any#Char$Field', type: 'number' },
- { name: 'kubernetes.something.something', type: 'number' },
- { name: '@timestamp', type: 'date' },
-];
-const enrichFields = [
- { name: 'otherField', type: 'string' },
- { name: 'yetAnotherField', type: 'number' },
-];
-// eslint-disable-next-line @typescript-eslint/naming-convention
-const unsupported_field = [{ name: 'unsupported_field', type: 'unsupported' }];
-const indexes = [
- 'a_index',
- 'index',
- 'other_index',
- '.secret_index',
- 'my-index',
- 'unsupported_index',
-];
-const policies = [
- {
- name: 'policy',
- sourceIndices: ['enrich_index'],
- matchField: 'otherStringField',
- enrichFields: ['otherField', 'yetAnotherField'],
- },
- {
- name: 'policy$',
- sourceIndices: ['enrich_index'],
- matchField: 'otherStringField',
- enrichFields: ['otherField', 'yetAnotherField'],
- },
-];
+import {
+ fields,
+ enrichFields,
+ getCallbackMocks,
+ indexes,
+ policies,
+ unsupported_field,
+} from '../__tests__/helpers';
const NESTING_LEVELS = 4;
const NESTED_DEPTHS = Array(NESTING_LEVELS)
.fill(0)
.map((_, i) => i + 1);
-function getCallbackMocks() {
- return {
- getFieldsFor: jest.fn(async ({ query }) => {
- if (/enrich/.test(query)) {
- return enrichFields;
- }
- if (/unsupported_index/.test(query)) {
- return unsupported_field;
- }
- if (/dissect|grok/.test(query)) {
- return [{ name: 'firstWord', type: 'string' }];
- }
- return fields;
- }),
- getSources: jest.fn(async () =>
- indexes.map((name) => ({
- name,
- hidden: name.startsWith('.'),
- }))
- ),
- getPolicies: jest.fn(async () => policies),
- };
-}
-
const toInteger = evalFunctionDefinitions.find(({ name }) => name === 'to_integer')!;
const toStringSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_string')!;
const toDateSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_datetime')!;
From 3701edc6a2b084009c3073b13f744497faa2f740 Mon Sep 17 00:00:00 2001
From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
Date: Fri, 31 May 2024 16:38:02 +0200
Subject: [PATCH 10/46] [Fleet] Fix bulk install package with force flag when
package is already installed (#184580)
## Summary
Closes https://github.com/elastic/kibana/issues/184491
Small fix for bulk install package API force flag, to allow reinstalling
a package if already installed.
To verify:
```
# Install Apache integration
POST kbn:/api/fleet/epm/packages/apache
# Install older version with force
POST kbn:/api/fleet/epm/packages/_bulk
{
"packages": [
{
"name": "apache",
"version": "1.17.2"
}
],
"force": true
}
# Expect 1.17.2 to be installed
{
"items": [
{
"name": "apache",
"version": "1.17.2",
"result": {
"assets": [
...
],
"status": "installed",
"installType": "install",
"installSource": "registry"
}
}
],
```
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../epm/packages/bulk_install_packages.ts | 48 ++++++++++---------
.../fleet/server/services/preconfiguration.ts | 1 +
.../apis/epm/bulk_install.ts | 24 +++++++++-
.../fleet_api_integration/apis/epm/index.js | 1 +
4 files changed, 50 insertions(+), 24 deletions(-)
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts
index 74dc880477ff9..08bc17f193bd7 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts
@@ -29,6 +29,7 @@ interface BulkInstallPackagesParams {
preferredSource?: 'registry' | 'bundled';
prerelease?: boolean;
authorizationHeader?: HTTPAuthorizationHeader | null;
+ skipIfInstalled?: boolean;
}
export async function bulkInstallPackages({
@@ -39,6 +40,7 @@ export async function bulkInstallPackages({
force,
prerelease,
authorizationHeader,
+ skipIfInstalled,
}: BulkInstallPackagesParams): Promise {
const logger = appContextService.getLogger();
@@ -91,28 +93,30 @@ export async function bulkInstallPackages({
}
const pkgKeyProps = result.value;
- const installedPackageResult = await isPackageVersionOrLaterInstalled({
- savedObjectsClient,
- pkgName: pkgKeyProps.name,
- pkgVersion: pkgKeyProps.version,
- });
-
- if (installedPackageResult) {
- const {
- name,
- version,
- installed_es: installedEs,
- installed_kibana: installedKibana,
- } = installedPackageResult.package;
- return {
- name,
- version,
- result: {
- assets: [...installedEs, ...installedKibana],
- status: 'already_installed',
- installType: 'unknown',
- } as InstallResult,
- };
+ if (!force || skipIfInstalled) {
+ const installedPackageResult = await isPackageVersionOrLaterInstalled({
+ savedObjectsClient,
+ pkgName: pkgKeyProps.name,
+ pkgVersion: pkgKeyProps.version,
+ });
+
+ if (installedPackageResult) {
+ const {
+ name,
+ version,
+ installed_es: installedEs,
+ installed_kibana: installedKibana,
+ } = installedPackageResult.package;
+ return {
+ name,
+ version,
+ result: {
+ assets: [...installedEs, ...installedKibana],
+ status: 'already_installed',
+ installType: 'unknown',
+ } as InstallResult,
+ };
+ }
}
const pkgkey = Registry.pkgToPkgKey(pkgKeyProps);
diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts
index 04df94d89b636..02c43d2f7b1c4 100644
--- a/x-pack/plugins/fleet/server/services/preconfiguration.ts
+++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts
@@ -100,6 +100,7 @@ export async function ensurePreconfiguredPackagesAndPolicies(
esClient,
packagesToInstall,
force: true, // Always force outdated packages to be installed if a later version isn't installed
+ skipIfInstalled: true, // force flag alone would reinstall packages that are already installed
spaceId,
});
diff --git a/x-pack/test/fleet_api_integration/apis/epm/bulk_install.ts b/x-pack/test/fleet_api_integration/apis/epm/bulk_install.ts
index 7b483c06e36b5..790fcd13873ea 100644
--- a/x-pack/test/fleet_api_integration/apis/epm/bulk_install.ts
+++ b/x-pack/test/fleet_api_integration/apis/epm/bulk_install.ts
@@ -52,6 +52,26 @@ export default function (providerContext: FtrProviderContext) {
await uninstallPackage(pkgName, pkgOlderVersion);
});
+ it('should install an older version if force is true when package is already installed', async () => {
+ // install latest package
+ await supertest
+ .post(`/api/fleet/epm/packages/_bulk?prerelease=true`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({ packages: [pkgName] })
+ .expect(200);
+
+ const response = await supertest
+ .post(`/api/fleet/epm/packages/_bulk?prerelease=true`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({ packages: [{ name: pkgName, version: pkgOlderVersion }], force: true })
+ .expect(200);
+
+ expect(response.body.items.length).equal(1);
+ expect(response.body.items[0].version).equal(pkgOlderVersion);
+
+ await uninstallPackage(pkgName, pkgOlderVersion);
+ });
+
it('should reject installing an older version if force is false', async () => {
const response = await supertest
.post(`/api/fleet/epm/packages/_bulk?prerelease=true`)
@@ -59,8 +79,8 @@ export default function (providerContext: FtrProviderContext) {
.send({ packages: [{ name: pkgName, version: pkgOlderVersion }] })
.expect(200);
- expect(response.body.response[0].statusCode).equal(400);
- expect(response.body.response[0].error).equal(
+ expect(response.body.items[0].statusCode).equal(400);
+ expect(response.body.items[0].error).equal(
'multiple_versions-0.1.0 is out-of-date and cannot be installed or updated'
);
});
diff --git a/x-pack/test/fleet_api_integration/apis/epm/index.js b/x-pack/test/fleet_api_integration/apis/epm/index.js
index 3c4315eff20cb..3caed7da79f65 100644
--- a/x-pack/test/fleet_api_integration/apis/epm/index.js
+++ b/x-pack/test/fleet_api_integration/apis/epm/index.js
@@ -32,6 +32,7 @@ export default function loadTests({ loadTestFile, getService }) {
loadTestFile(require.resolve('./install_tsds_disable'));
loadTestFile(require.resolve('./install_tag_assets'));
loadTestFile(require.resolve('./bulk_upgrade'));
+ loadTestFile(require.resolve('./bulk_install'));
loadTestFile(require.resolve('./update_assets'));
loadTestFile(require.resolve('./data_stream'));
loadTestFile(require.resolve('./package_install_complete'));
From 464f797a73ab7377bfd7f64a54695d0b1a26f8d2 Mon Sep 17 00:00:00 2001
From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
Date: Fri, 31 May 2024 16:38:51 +0200
Subject: [PATCH 11/46] [Fleet] Prevent concurrent runs of Fleet setup
(#183636)
Closes https://github.com/elastic/ingest-dev/issues/3346
- [x] Unit and integration tests are created or updated
- [x] Turn down info logging
The linked issue seems to be caused by multiple kibana instances running
Fleet setup at the same time, trying to create the preconfigured cloud
policy concurrently, and in case of failures, the agent policy is left
with a revision with no inputs, this way preventing fleet-server to
start properly.
See the concurrent errors in the logs:
https://platform-logging.kb.us-west2.gcp.elastic-cloud.com/app/r/s/tUpMP
This fix introduces a `fleet-setup-lock` SO type, which is used to
create a document as a lock by Fleet setup, and is deleted when the
setup is completed. Concurrent calls to Fleet setup will return early if
this doc exists.
To verify:
Run the test `./run_fleet_setup_parallel.sh` from local kibana, and
verify the generated logs that only one of them ran Fleet setup.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../current_fields.json | 5 +
.../current_mappings.json | 13 +
run_fleet_setup_parallel.sh | 7 +
.../check_registered_types.test.ts | 1 +
.../group3/type_registrations.test.ts | 1 +
.../group5/dot_kibana_split.test.ts | 1 +
.../plugins/fleet/common/constants/index.ts | 2 +-
.../fleet/common/constants/saved_objects.ts | 2 +
.../fleet/common/types/models/agent_policy.ts | 1 +
.../common/types/models/fleet_setup_lock.ts | 12 +
.../fleet/common/types/models/index.ts | 1 +
.../plugins/fleet/server/constants/index.ts | 1 +
.../fleet/server/integration_tests/es.test.ts | 50 ++++
.../integration_tests/fleet_setup.test.ts | 278 ++++++++++++++++++
x-pack/plugins/fleet/server/plugin.ts | 10 +-
.../fleet/server/saved_objects/index.ts | 17 ++
.../fleet/server/services/agent_policy.ts | 15 +-
.../services/epm/packages/_install_package.ts | 6 +-
.../fleet/server/services/preconfiguration.ts | 9 +-
.../preconfiguration/reset_agent_policies.ts | 1 -
.../fleet/server/services/setup.test.ts | 57 ++++
x-pack/plugins/fleet/server/services/setup.ts | 82 +++++-
.../fleet/server/types/so_attributes.ts | 1 +
.../apis/agent_policy/agent_policy.ts | 8 +-
24 files changed, 565 insertions(+), 16 deletions(-)
create mode 100755 run_fleet_setup_parallel.sh
create mode 100644 x-pack/plugins/fleet/common/types/models/fleet_setup_lock.ts
create mode 100644 x-pack/plugins/fleet/server/integration_tests/es.test.ts
create mode 100644 x-pack/plugins/fleet/server/integration_tests/fleet_setup.test.ts
diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json
index 5300aa8d4cd80..43ceb7b99b232 100644
--- a/packages/kbn-check-mappings-update-cli/current_fields.json
+++ b/packages/kbn-check-mappings-update-cli/current_fields.json
@@ -457,6 +457,11 @@
"proxy_headers",
"url"
],
+ "fleet-setup-lock": [
+ "started_at",
+ "status",
+ "uuid"
+ ],
"fleet-uninstall-tokens": [
"policy_id",
"token_plain"
diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json
index 628dd5c6dbc16..92d6aef0bb6ad 100644
--- a/packages/kbn-check-mappings-update-cli/current_mappings.json
+++ b/packages/kbn-check-mappings-update-cli/current_mappings.json
@@ -1546,6 +1546,19 @@
}
}
},
+ "fleet-setup-lock": {
+ "properties": {
+ "started_at": {
+ "type": "date"
+ },
+ "status": {
+ "type": "keyword"
+ },
+ "uuid": {
+ "type": "text"
+ }
+ }
+ },
"fleet-uninstall-tokens": {
"dynamic": false,
"properties": {
diff --git a/run_fleet_setup_parallel.sh b/run_fleet_setup_parallel.sh
new file mode 100755
index 0000000000000..e7ab3f5d33338
--- /dev/null
+++ b/run_fleet_setup_parallel.sh
@@ -0,0 +1,7 @@
+node scripts/jest_integration.js x-pack/plugins/fleet/server/integration_tests/es.test.ts &
+
+sleep 5
+node scripts/jest_integration.js x-pack/plugins/fleet/server/integration_tests/fleet_setup.test.ts &
+node scripts/jest_integration.js x-pack/plugins/fleet/server/integration_tests/fleet_setup.test.ts &
+node scripts/jest_integration.js x-pack/plugins/fleet/server/integration_tests/fleet_setup.test.ts &
+exit 0
\ No newline at end of file
diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts
index 5e98b0d0245ca..10d71344a0a2b 100644
--- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts
+++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts
@@ -101,6 +101,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f",
"fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e",
"fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d",
+ "fleet-setup-lock": "0dc784792c79b5af5a6e6b5dcac06b0dbaa90bde",
"fleet-uninstall-tokens": "ed8aa37e3cdd69e4360709e64944bb81cae0c025",
"graph-workspace": "5cc6bb1455b078fd848c37324672163f09b5e376",
"guided-onboarding-guide-state": "d338972ed887ac480c09a1a7fbf582d6a3827c91",
diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts
index 2c85df9e6d50e..f6a9bfd089008 100644
--- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts
+++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts
@@ -69,6 +69,7 @@ const previouslyRegisteredTypes = [
'fleet-preconfiguration-deletion-record',
'fleet-proxy',
'fleet-uninstall-tokens',
+ 'fleet-setup-lock',
'graph-workspace',
'guided-setup-state',
'guided-onboarding-guide-state',
diff --git a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts
index 0ad0e283711a2..c0a4c73b1664c 100644
--- a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts
+++ b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts
@@ -221,6 +221,7 @@ describe('split .kibana index into multiple system indices', () => {
"fleet-message-signing-keys",
"fleet-preconfiguration-deletion-record",
"fleet-proxy",
+ "fleet-setup-lock",
"fleet-uninstall-tokens",
"graph-workspace",
"guided-onboarding-guide-state",
diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts
index 51d51db9e761f..6e9af1e7d269c 100644
--- a/x-pack/plugins/fleet/common/constants/index.ts
+++ b/x-pack/plugins/fleet/common/constants/index.ts
@@ -6,7 +6,7 @@
*/
export { INTEGRATIONS_PLUGIN_ID, PLUGIN_ID } from './plugin';
-export { INGEST_SAVED_OBJECT_INDEX } from './saved_objects';
+export { INGEST_SAVED_OBJECT_INDEX, FLEET_SETUP_LOCK_TYPE } from './saved_objects';
export * from './routes';
export * from './agent';
export * from './agent_policy';
diff --git a/x-pack/plugins/fleet/common/constants/saved_objects.ts b/x-pack/plugins/fleet/common/constants/saved_objects.ts
index 3bca180cb32db..542a03e8fc281 100644
--- a/x-pack/plugins/fleet/common/constants/saved_objects.ts
+++ b/x-pack/plugins/fleet/common/constants/saved_objects.ts
@@ -6,3 +6,5 @@
*/
export const INGEST_SAVED_OBJECT_INDEX = '.kibana_ingest';
+
+export const FLEET_SETUP_LOCK_TYPE = 'fleet-setup-lock';
diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts
index 7243c54007fd9..eabe321d62604 100644
--- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts
+++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts
@@ -62,6 +62,7 @@ export interface AgentPolicy extends Omit {
agents?: number;
unprivileged_agents?: number;
is_protected: boolean;
+ version?: string;
}
export interface FullAgentPolicyInputStream {
diff --git a/x-pack/plugins/fleet/common/types/models/fleet_setup_lock.ts b/x-pack/plugins/fleet/common/types/models/fleet_setup_lock.ts
new file mode 100644
index 0000000000000..8433b1efa8d22
--- /dev/null
+++ b/x-pack/plugins/fleet/common/types/models/fleet_setup_lock.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 interface FleetSetupLock {
+ status: string;
+ uuid: string;
+ started_at: string;
+}
diff --git a/x-pack/plugins/fleet/common/types/models/index.ts b/x-pack/plugins/fleet/common/types/models/index.ts
index 5af1294e52657..a873c2f4bedde 100644
--- a/x-pack/plugins/fleet/common/types/models/index.ts
+++ b/x-pack/plugins/fleet/common/types/models/index.ts
@@ -20,3 +20,4 @@ export * from './fleet_server_policy_config';
export * from './fleet_proxy';
export * from './secret';
export * from './setup_technology';
+export * from './fleet_setup_lock';
diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts
index bf5179546c3f0..87db7250513be 100644
--- a/x-pack/plugins/fleet/server/constants/index.ts
+++ b/x-pack/plugins/fleet/server/constants/index.ts
@@ -90,6 +90,7 @@ export {
OUTPUT_SECRETS_MINIMUM_FLEET_SERVER_VERSION,
// outputs
OUTPUT_HEALTH_DATA_STREAM,
+ FLEET_SETUP_LOCK_TYPE,
type PrivilegeMapObject,
} from '../../common/constants';
diff --git a/x-pack/plugins/fleet/server/integration_tests/es.test.ts b/x-pack/plugins/fleet/server/integration_tests/es.test.ts
new file mode 100644
index 0000000000000..5fad8894b0985
--- /dev/null
+++ b/x-pack/plugins/fleet/server/integration_tests/es.test.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 { TestElasticsearchUtils } from '@kbn/core-test-helpers-kbn-server';
+import { createTestServers } from '@kbn/core-test-helpers-kbn-server';
+
+/**
+ * Verifies that multiple Kibana instances running in parallel will not create duplicate preconfiguration objects.
+ */
+describe.skip('Fleet setup preconfiguration with multiple instances Kibana', () => {
+ let esServer: TestElasticsearchUtils;
+
+ const startServers = async () => {
+ const { startES } = createTestServers({
+ adjustTimeout: (t) => jest.setTimeout(t),
+ settings: {
+ es: {
+ license: 'trial',
+ },
+ },
+ });
+
+ esServer = await startES();
+ };
+
+ const stopServers = async () => {
+ if (esServer) {
+ await esServer.stop();
+ }
+
+ await new Promise((res) => setTimeout(res, 10000));
+ };
+
+ beforeEach(async () => {
+ await startServers();
+ });
+
+ afterEach(async () => {
+ await stopServers();
+ });
+
+ describe('startES', () => {
+ it('start es', async () => {
+ await new Promise((resolve) => setTimeout(resolve, 60000));
+ });
+ });
+});
diff --git a/x-pack/plugins/fleet/server/integration_tests/fleet_setup.test.ts b/x-pack/plugins/fleet/server/integration_tests/fleet_setup.test.ts
new file mode 100644
index 0000000000000..b5c300f9fa59a
--- /dev/null
+++ b/x-pack/plugins/fleet/server/integration_tests/fleet_setup.test.ts
@@ -0,0 +1,278 @@
+/*
+ * 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 Path from 'path';
+
+import { range } from 'lodash';
+
+import type { ISavedObjectsRepository } from '@kbn/core/server';
+import type { TestElasticsearchUtils, createRoot } from '@kbn/core-test-helpers-kbn-server';
+import { getSupertest, createRootWithCorePlugins } from '@kbn/core-test-helpers-kbn-server';
+
+import type {
+ AgentPolicySOAttributes,
+ Installation,
+ OutputSOAttributes,
+ PackagePolicySOAttributes,
+} from '../types';
+
+type Root = ReturnType;
+
+const startAndWaitForFleetSetup = async (root: Root) => {
+ const start = await root.start();
+
+ const isFleetSetupRunning = async () => {
+ const statusApi = getSupertest(root, 'get', '/api/status');
+ const resp: any = await statusApi.send();
+ const fleetStatus = resp.body?.status?.plugins?.fleet;
+ if (fleetStatus?.meta?.error) {
+ throw new Error(`Setup failed: ${JSON.stringify(fleetStatus)}`);
+ }
+
+ const isRunning = !fleetStatus || fleetStatus?.summary === 'Fleet is setting up';
+ return isRunning;
+ };
+
+ while (await isFleetSetupRunning()) {
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ }
+
+ return start;
+};
+
+const createAndSetupRoot = async (config?: object, index?: number) => {
+ const root = createRootWithCorePlugins(
+ {
+ xpack: {
+ fleet: config,
+ },
+ logging: {
+ appenders: {
+ file: {
+ type: 'file',
+ fileName: Path.join(__dirname, `logs_${Math.floor(Math.random() * 100)}.log`),
+ layout: {
+ type: 'json',
+ },
+ },
+ },
+ loggers: [
+ {
+ name: 'root',
+ appenders: ['file'],
+ },
+ {
+ name: 'plugins.fleet',
+ appenders: ['file'],
+ level: 'info',
+ },
+ ],
+ },
+ },
+ { oss: false }
+ );
+
+ await root.preboot();
+ await root.setup();
+ return root;
+};
+
+/**
+ * Verifies that multiple Kibana instances running in parallel will not create duplicate preconfiguration objects.
+ */
+describe.skip('Fleet setup preconfiguration with multiple instances Kibana', () => {
+ let esServer: TestElasticsearchUtils;
+ let roots: Root[] = [];
+
+ const registryUrl = 'https://epr.elastic.co/';
+
+ const addRoots = async (n: number) => {
+ const newRoots = await Promise.all(
+ range(n).map((val, index) => createAndSetupRoot(preconfiguration, index))
+ );
+ newRoots.forEach((r) => roots.push(r));
+ return newRoots;
+ };
+
+ const startRoots = async () => {
+ return await Promise.all(roots.map(startAndWaitForFleetSetup));
+ };
+
+ const stopServers = async () => {
+ for (const root of roots) {
+ await root.shutdown();
+ }
+ roots = [];
+
+ if (esServer) {
+ await esServer.stop();
+ }
+
+ await new Promise((res) => setTimeout(res, 10000));
+ };
+
+ afterEach(async () => {
+ await stopServers();
+ });
+
+ describe('preconfiguration setup', () => {
+ it('sets up Fleet correctly', async () => {
+ await addRoots(1);
+ const [root1Start] = await startRoots();
+ const soClient = root1Start.savedObjects.createInternalRepository();
+
+ const esClient = root1Start.elasticsearch.client.asInternalUser;
+ await new Promise((res) => setTimeout(res, 1000));
+
+ try {
+ const res = await esClient.search({
+ index: '.fleet-policies',
+ q: 'policy_id:policy-elastic-agent-on-cloud',
+ sort: 'revision_idx:desc',
+ _source: ['revision_idx', '@timestamp'],
+ });
+ // eslint-disable-next-line no-console
+ console.log(JSON.stringify(res, null, 2));
+
+ expect(res.hits.hits.length).toBeGreaterThanOrEqual(1);
+ expect((res.hits.hits[0]._source as any)?.data?.inputs).not.toEqual([]);
+ } catch (err) {
+ if (err.statusCode === 404) {
+ return;
+ }
+ throw err;
+ }
+ await expectFleetSetupState(soClient);
+ });
+ });
+
+ const preconfiguration = {
+ registryUrl,
+ packages: [
+ {
+ name: 'fleet_server',
+ version: 'latest',
+ },
+ {
+ name: 'apm',
+ version: 'latest',
+ },
+ {
+ name: 'endpoint',
+ version: 'latest',
+ },
+ {
+ name: 'log',
+ version: 'latest',
+ },
+ ],
+ outputs: [
+ {
+ name: 'Preconfigured output',
+ id: 'preconfigured-output',
+ type: 'elasticsearch',
+ hosts: ['http://127.0.0.1:9200'],
+ },
+ ],
+ fleetServerHosts: [
+ {
+ id: 'fleet-server',
+ name: 'Fleet Server',
+ is_default: true,
+ host_urls: ['https://192.168.178.216:8220'],
+ },
+ ],
+ agentPolicies: [
+ {
+ name: 'Elastic Cloud agent policy',
+ id: 'policy-elastic-agent-on-cloud',
+ data_output_id: 'preconfigured-output',
+ monitoring_output_id: 'preconfigured-output',
+ is_managed: true,
+ is_default_fleet_server: true,
+ package_policies: [
+ {
+ name: 'elastic-cloud-fleet-server',
+ package: {
+ name: 'fleet_server',
+ },
+ inputs: [
+ {
+ type: 'fleet-server',
+ keep_enabled: true,
+ vars: [{ name: 'host', value: '127.0.0.1:8220', frozen: true }],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ async function expectFleetSetupState(soClient: ISavedObjectsRepository) {
+ // Assert setup state
+ const agentPolicies = await soClient.find({
+ type: 'ingest-agent-policies',
+ perPage: 10000,
+ });
+ expect(agentPolicies.saved_objects).toHaveLength(1);
+ expect(agentPolicies.saved_objects.map((ap) => ap.attributes)).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ name: 'Elastic Cloud agent policy',
+ is_managed: true,
+ is_default_fleet_server: true,
+ data_output_id: 'preconfigured-output',
+ }),
+ ])
+ );
+
+ const packagePolicies = await soClient.find({
+ type: 'ingest-package-policies',
+ perPage: 10000,
+ });
+ expect(packagePolicies.saved_objects.length).toBeGreaterThanOrEqual(1);
+ expect(packagePolicies.saved_objects.map((pp) => pp.attributes.name)).toEqual(
+ expect.arrayContaining(['elastic-cloud-fleet-server'])
+ );
+
+ const outputs = await soClient.find({
+ type: 'ingest-outputs',
+ perPage: 10000,
+ });
+ expect(outputs.saved_objects).toHaveLength(2);
+ expect(outputs.saved_objects.map((o) => o.attributes)).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ name: 'default',
+ is_default: true,
+ is_default_monitoring: true,
+ type: 'elasticsearch',
+ output_id: 'fleet-default-output',
+ hosts: ['http://localhost:9200'],
+ }),
+ expect.objectContaining({
+ name: 'Preconfigured output',
+ is_default: false,
+ is_default_monitoring: false,
+ type: 'elasticsearch',
+ output_id: 'preconfigured-output',
+ hosts: ['http://127.0.0.1:9200'],
+ }),
+ ])
+ );
+
+ const packages = await soClient.find({
+ type: 'epm-packages',
+ perPage: 10000,
+ });
+ expect(packages.saved_objects.length).toBeGreaterThanOrEqual(1);
+ expect(packages.saved_objects.map((p) => p.attributes.name)).toEqual(
+ expect.arrayContaining(['fleet_server'])
+ );
+ }
+});
diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts
index 02e6d7baf9577..103c44ff1f8d4 100644
--- a/x-pack/plugins/fleet/server/plugin.ts
+++ b/x-pack/plugins/fleet/server/plugin.ts
@@ -656,18 +656,24 @@ export class FleetPlugin
)
.toPromise();
+ const randomIntFromInterval = (min: number, max: number) => {
+ return Math.floor(Math.random() * (max - min + 1) + min);
+ };
+
// Retry Fleet setup w/ backoff
await backOff(
async () => {
await setupFleet(
new SavedObjectsClient(core.savedObjects.createInternalRepository()),
- core.elasticsearch.client.asInternalUser
+ core.elasticsearch.client.asInternalUser,
+ { useLock: true }
);
},
{
numOfAttempts: setupAttempts,
+ delayFirstAttempt: true,
// 1s initial backoff
- startingDelay: 1000,
+ startingDelay: randomIntFromInterval(100, 1000),
// 5m max backoff
maxDelay: 60000 * 5,
timeMultiple: 2,
diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts
index bb8eaba04a6c7..ad958bc986d00 100644
--- a/x-pack/plugins/fleet/server/saved_objects/index.ts
+++ b/x-pack/plugins/fleet/server/saved_objects/index.ts
@@ -23,6 +23,7 @@ import {
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
INGEST_SAVED_OBJECT_INDEX,
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
+ FLEET_SETUP_LOCK_TYPE,
} from '../constants';
import { migrateSyntheticsPackagePolicyToV8120 } from './migrations/synthetics/to_v8_12_0';
@@ -100,6 +101,22 @@ export const getSavedObjectTypes = (
const { useSpaceAwareness } = options;
return {
+ [FLEET_SETUP_LOCK_TYPE]: {
+ name: FLEET_SETUP_LOCK_TYPE,
+ indexPattern: INGEST_SAVED_OBJECT_INDEX,
+ hidden: false,
+ namespaceType: 'agnostic',
+ management: {
+ importableAndExportable: false,
+ },
+ mappings: {
+ properties: {
+ status: { type: 'keyword' },
+ uuid: { type: 'text' },
+ started_at: { type: 'date' },
+ },
+ },
+ },
// Deprecated
[GLOBAL_SETTINGS_SAVED_OBJECT_TYPE]: {
name: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts
index 3e564d0fe8536..6469479b77ef5 100644
--- a/x-pack/plugins/fleet/server/services/agent_policy.ts
+++ b/x-pack/plugins/fleet/server/services/agent_policy.ts
@@ -183,7 +183,11 @@ class AgentPolicyService {
if (options.bumpRevision || options.removeProtection) {
await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'updated', id);
}
- logger.debug(`Agent policy ${id} update completed`);
+ logger.debug(
+ `Agent policy ${id} update completed, revision: ${
+ options.bumpRevision ? existingAgentPolicy.revision + 1 : existingAgentPolicy.revision
+ }`
+ );
return (await this.get(soClient, id)) as AgentPolicy;
}
@@ -389,6 +393,7 @@ class AgentPolicyService {
const agentPolicy = {
id: agentPolicySO.id,
+ version: agentPolicySO.version,
...agentPolicySO.attributes,
};
@@ -1041,6 +1046,14 @@ class AgentPolicyService {
return acc;
}, [] as FleetServerPolicy[]);
+ appContextService
+ .getLogger()
+ .debug(
+ `Deploying policies: ${fleetServerPolicies
+ .map((pol) => `${pol.policy_id}:${pol.revision_idx}`)
+ .join(', ')}`
+ );
+
const fleetServerPoliciesBulkBody = fleetServerPolicies.flatMap((fleetServerPolicy) => [
{
index: {
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts
index 4a6cb0306a9cb..780f1ceb60566 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts
@@ -103,7 +103,9 @@ export async function _installPackage({
const hasExceededTimeout =
Date.now() - Date.parse(installedPkg.attributes.install_started_at) <
MAX_TIME_COMPLETE_INSTALL;
- logger.debug(`Package install - Install status ${installedPkg.attributes.install_status}`);
+ logger.debug(
+ `Package install - Install status ${pkgName}-${pkgVersion}: ${installedPkg.attributes.install_status}`
+ );
// if the installation is currently running, don't try to install
// instead, only return already installed assets
@@ -142,7 +144,7 @@ export async function _installPackage({
});
}
} else {
- logger.debug(`Package install - Create installation`);
+ logger.debug(`Package install - Create installation ${pkgName}-${pkgVersion}`);
await createInstallation({
savedObjectsClient,
packageInfo,
diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts
index 02c43d2f7b1c4..9d0e7bc2b151a 100644
--- a/x-pack/plugins/fleet/server/services/preconfiguration.ts
+++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts
@@ -294,7 +294,14 @@ export async function ensurePreconfiguredPackagesAndPolicies(
packagePolicy.name === installablePackagePolicy.packagePolicy.name
);
});
- logger.debug(`Adding preconfigured package policies ${packagePoliciesToAdd}`);
+ logger.debug(
+ `Adding preconfigured package policies ${JSON.stringify(
+ packagePoliciesToAdd.map((pol) => ({
+ name: pol.packagePolicy.name,
+ package: pol.installedPackage.name,
+ }))
+ )}`
+ );
const s = apm.startSpan('Add preconfigured package policies', 'preconfiguration');
await addPreconfiguredPolicyPackages(
esClient,
diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts b/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts
index 6966f0a217241..be770c7f4af90 100644
--- a/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts
+++ b/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts
@@ -34,7 +34,6 @@ export async function resetPreconfiguredAgentPolicies(
await _deleteExistingData(soClient, esClient, logger, agentPolicyId);
await _deleteGhostPackagePolicies(soClient, esClient, logger);
await _deletePreconfigurationDeleteRecord(soClient, logger, agentPolicyId);
-
await setupFleet(soClient, esClient);
}
diff --git a/x-pack/plugins/fleet/server/services/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts
index fbabfde0316f9..03a52c27abffe 100644
--- a/x-pack/plugins/fleet/server/services/setup.test.ts
+++ b/x-pack/plugins/fleet/server/services/setup.test.ts
@@ -68,6 +68,8 @@ describe('setupFleet', () => {
soClient.get.mockResolvedValue({ attributes: {} } as any);
soClient.find.mockResolvedValue({ saved_objects: [] } as any);
soClient.bulkGet.mockResolvedValue({ saved_objects: [] } as any);
+ soClient.create.mockResolvedValue({ attributes: {} } as any);
+ soClient.delete.mockResolvedValue({});
});
afterEach(async () => {
@@ -134,4 +136,59 @@ describe('setupFleet', () => {
],
});
});
+
+ it('should create and delete lock if not exists', async () => {
+ soClient.get.mockRejectedValue({ isBoom: true, output: { statusCode: 404 } } as any);
+
+ const result = await setupFleet(soClient, esClient, { useLock: true });
+
+ expect(result).toEqual({
+ isInitialized: true,
+ nonFatalErrors: [],
+ });
+ expect(soClient.create).toHaveBeenCalledWith('fleet-setup-lock', expect.anything(), {
+ id: 'fleet-setup-lock',
+ });
+ expect(soClient.delete).toHaveBeenCalledWith('fleet-setup-lock', 'fleet-setup-lock', {
+ refresh: true,
+ });
+ });
+
+ it('should return not initialized if lock exists', async () => {
+ const result = await setupFleet(soClient, esClient, { useLock: true });
+
+ expect(result).toEqual({
+ isInitialized: false,
+ nonFatalErrors: [],
+ });
+ expect(soClient.create).not.toHaveBeenCalled();
+ expect(soClient.delete).not.toHaveBeenCalled();
+ });
+
+ it('should return not initialized if lock could not be created', async () => {
+ soClient.get.mockRejectedValue({ isBoom: true, output: { statusCode: 404 } } as any);
+ soClient.create.mockRejectedValue({ isBoom: true, output: { statusCode: 409 } } as any);
+ const result = await setupFleet(soClient, esClient, { useLock: true });
+
+ expect(result).toEqual({
+ isInitialized: false,
+ nonFatalErrors: [],
+ });
+ expect(soClient.delete).not.toHaveBeenCalled();
+ });
+
+ it('should delete previous lock if created more than 1 hour ago', async () => {
+ soClient.get.mockResolvedValue({
+ attributes: { started_at: new Date(Date.now() - 60 * 60 * 1000 - 1000).toISOString() },
+ } as any);
+
+ const result = await setupFleet(soClient, esClient, { useLock: true });
+
+ expect(result).toEqual({
+ isInitialized: true,
+ nonFatalErrors: [],
+ });
+ expect(soClient.create).toHaveBeenCalled();
+ expect(soClient.delete).toHaveBeenCalledTimes(2);
+ });
});
diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts
index af2c19c42c70b..d1e29147a2104 100644
--- a/x-pack/plugins/fleet/server/services/setup.ts
+++ b/x-pack/plugins/fleet/server/services/setup.ts
@@ -11,14 +11,15 @@ import apm from 'elastic-apm-node';
import { compact } from 'lodash';
import pMap from 'p-map';
+import { v4 as uuidv4 } from 'uuid';
import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants';
import { MessageSigningError } from '../../common/errors';
-import { AUTO_UPDATE_PACKAGES } from '../../common/constants';
+import { AUTO_UPDATE_PACKAGES, FLEET_SETUP_LOCK_TYPE } from '../../common/constants';
import type { PreconfigurationError } from '../../common/constants';
-import type { DefaultPackagesInstallationError } from '../../common/types';
+import type { DefaultPackagesInstallationError, FleetSetupLock } from '../../common/types';
import { appContextService } from './app_context';
import { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration';
@@ -65,11 +66,19 @@ export interface SetupStatus {
export async function setupFleet(
soClient: SavedObjectsClientContract,
- esClient: ElasticsearchClient
+ esClient: ElasticsearchClient,
+ options: {
+ useLock: boolean;
+ } = { useLock: false }
): Promise {
const t = apm.startTransaction('fleet-setup', 'fleet');
-
+ let created = false;
try {
+ if (options.useLock) {
+ const { created: isCreated, toReturn } = await createLock(soClient);
+ created = isCreated;
+ if (toReturn) return toReturn;
+ }
return await awaitIfPending(async () => createSetupSideEffects(soClient, esClient));
} catch (error) {
apm.captureError(error);
@@ -77,6 +86,71 @@ export async function setupFleet(
throw error;
} finally {
t.end();
+ // only delete lock if it was created by this instance
+ if (options.useLock && created) {
+ await deleteLock(soClient);
+ }
+ }
+}
+
+async function createLock(
+ soClient: SavedObjectsClientContract
+): Promise<{ created: boolean; toReturn?: SetupStatus }> {
+ const logger = appContextService.getLogger();
+ let created;
+ try {
+ // check if fleet setup is already started
+ const fleetSetupLock = await soClient.get(
+ FLEET_SETUP_LOCK_TYPE,
+ FLEET_SETUP_LOCK_TYPE
+ );
+
+ const LOCK_TIMEOUT = 60 * 60 * 1000; // 1 hour
+
+ // started more than 1 hour ago, delete previous lock
+ if (
+ fleetSetupLock.attributes.started_at &&
+ new Date(fleetSetupLock.attributes.started_at).getTime() < Date.now() - LOCK_TIMEOUT
+ ) {
+ await deleteLock(soClient);
+ } else {
+ logger.info('Fleet setup already in progress, abort setup');
+ return { created: false, toReturn: { isInitialized: false, nonFatalErrors: [] } };
+ }
+ } catch (error) {
+ if (error.isBoom && error.output.statusCode === 404) {
+ logger.debug('Fleet setup lock does not exist, continue setup');
+ }
+ }
+
+ try {
+ created = await soClient.create(
+ FLEET_SETUP_LOCK_TYPE,
+ {
+ status: 'in_progress',
+ uuid: uuidv4(),
+ started_at: new Date().toISOString(),
+ },
+ { id: FLEET_SETUP_LOCK_TYPE }
+ );
+ logger.debug(`Fleet setup lock created: ${JSON.stringify(created)}`);
+ } catch (error) {
+ logger.info(`Could not create fleet setup lock, abort setup: ${error}`);
+ return { created: false, toReturn: { isInitialized: false, nonFatalErrors: [] } };
+ }
+ return { created: !!created };
+}
+
+async function deleteLock(soClient: SavedObjectsClientContract) {
+ const logger = appContextService.getLogger();
+ try {
+ await soClient.delete(FLEET_SETUP_LOCK_TYPE, FLEET_SETUP_LOCK_TYPE, { refresh: true });
+ logger.debug(`Fleet setup lock deleted`);
+ } catch (error) {
+ // ignore 404 errors
+ if (error.statusCode !== 404) {
+ logger.error('Could not delete fleet setup lock', error);
+ }
}
}
diff --git a/x-pack/plugins/fleet/server/types/so_attributes.ts b/x-pack/plugins/fleet/server/types/so_attributes.ts
index ac084733b81d7..1b15ea9f869a9 100644
--- a/x-pack/plugins/fleet/server/types/so_attributes.ts
+++ b/x-pack/plugins/fleet/server/types/so_attributes.ts
@@ -65,6 +65,7 @@ export interface AgentPolicySOAttributes {
agents?: number;
overrides?: any | null;
global_data_tags?: Array<{ name: string; value: string | number }>;
+ version?: string;
}
export interface AgentSOAttributes {
diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts
index 4ae4704eb5f7b..76fe6e6e340fd 100644
--- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts
+++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts
@@ -517,7 +517,7 @@ export default function (providerContext: FtrProviderContext) {
})
.expect(200);
// eslint-disable-next-line @typescript-eslint/naming-convention
- const { id, updated_at, ...newPolicy } = item;
+ const { id, updated_at, version, ...newPolicy } = item;
expect(newPolicy).to.eql({
name: 'Copied policy',
@@ -947,7 +947,7 @@ export default function (providerContext: FtrProviderContext) {
.expect(200);
createdPolicyIds.push(updatedPolicy.id);
// eslint-disable-next-line @typescript-eslint/naming-convention
- const { id, updated_at, ...newPolicy } = updatedPolicy;
+ const { id, updated_at, version, ...newPolicy } = updatedPolicy;
expect(newPolicy).to.eql({
status: 'active',
@@ -1108,7 +1108,7 @@ export default function (providerContext: FtrProviderContext) {
})
.expect(200);
// eslint-disable-next-line @typescript-eslint/naming-convention
- const { id, updated_at, ...newPolicy } = updatedPolicy;
+ const { id, updated_at, version, ...newPolicy } = updatedPolicy;
createdPolicyIds.push(updatedPolicy.id);
expect(newPolicy).to.eql({
@@ -1168,7 +1168,7 @@ export default function (providerContext: FtrProviderContext) {
.expect(200);
// eslint-disable-next-line @typescript-eslint/naming-convention
- const { id, updated_at, ...newPolicy } = updatedPolicy;
+ const { id, updated_at, version, ...newPolicy } = updatedPolicy;
expect(newPolicy).to.eql({
status: 'active',
From 06fc22a0f15e692857ba689a7b0ddec91ed2dac2 Mon Sep 17 00:00:00 2001
From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
Date: Fri, 31 May 2024 10:51:02 -0400
Subject: [PATCH 12/46] [Security Solution] Adds unit tests for simple diff
algorithm (#184483)
## Summary
Adds unit tests in accordance to
https://github.com/elastic/kibana/issues/180158
Abstracts the `simpleDiffAlgorithm` function used in the prebuilt rule
upgrade workflow into `singleLineStringDiffAlgorithm` and
`numberDiffAlgorithm` and adds unit tests for both cases
Addresses the following test cases defined in the [related RFC
section](https://github.com/elastic/kibana/blob/4c9ab711b2a59ebec60ce5f1de18122d7405f9a0/x-pack/plugins/security_solution/docs/rfcs/detection_response/prebuilt_rules_customization.md#single-line-string-fields)
table
- [x] AAA
- [x] ABA
- [x] AAB
- [x] ABB
- [x] ABC
- [x] -AA
- [x] -AB
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
.../algorithms/number_diff_algorithm.test.ts | 151 ++++++++++++++++++
.../algorithms/number_diff_algorithm.ts | 10 ++
.../algorithms/simple_diff_algorithm.ts | 7 +-
.../single_line_string_diff_algorithm.test.ts | 151 ++++++++++++++++++
.../single_line_string_diff_algorithm.ts | 10 ++
5 files changed, 328 insertions(+), 1 deletion(-)
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts
new file mode 100644
index 0000000000000..43f6c9ed97e9d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts
@@ -0,0 +1,151 @@
+/*
+ * 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 { ThreeVersionsOf } from '../../../../../../../../common/api/detection_engine';
+import {
+ ThreeWayDiffOutcome,
+ ThreeWayMergeOutcome,
+ MissingVersion,
+} from '../../../../../../../../common/api/detection_engine';
+import { numberDiffAlgorithm } from './number_diff_algorithm';
+
+describe('numberDiffAlgorithm', () => {
+ it('returns current_version as merged output if there is no update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 1,
+ current_version: 1,
+ target_version: 1,
+ };
+
+ const result = numberDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns current_version as merged output if current_version is different and there is no update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 1,
+ current_version: 2,
+ target_version: 1,
+ };
+
+ const result = numberDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns target_version as merged output if current_version is the same and there is an update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 1,
+ current_version: 1,
+ target_version: 2,
+ };
+
+ const result = numberDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.target_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Target,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns current_version as merged output if current version is different but it matches the update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 1,
+ current_version: 2,
+ target_version: 2,
+ };
+
+ const result = numberDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns current_version as merged output if all three versions are different', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 1,
+ current_version: 2,
+ target_version: 3,
+ };
+
+ const result = numberDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Conflict,
+ has_conflict: true,
+ })
+ );
+ });
+
+ describe('if base_version is missing', () => {
+ it('returns current_version as merged output if current_version and target_version are the same', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: MissingVersion,
+ current_version: 1,
+ target_version: 1,
+ };
+
+ const result = numberDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns target_version as merged output if current_version and target_version are different', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: MissingVersion,
+ current_version: 1,
+ target_version: 2,
+ };
+
+ const result = numberDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.target_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Target,
+ has_conflict: false,
+ })
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts
new file mode 100644
index 0000000000000..513d9047c7a5c
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.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.
+ */
+
+import { simpleDiffAlgorithm } from './simple_diff_algorithm';
+
+export const numberDiffAlgorithm = simpleDiffAlgorithm;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts
index f598e9da451c6..51d3d8fc22d58 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts
@@ -17,6 +17,11 @@ import {
ThreeWayMergeOutcome,
} from '../../../../../../../../common/api/detection_engine/prebuilt_rules';
+/**
+ * The default diff algorithm, diffs versions passed using a simple lodash `isEqual` comparison
+ *
+ * Meant to be used with primitive types (strings, numbers, booleans), NOT Arrays or Objects
+ */
export const simpleDiffAlgorithm = (
versions: ThreeVersionsOf
): ThreeWayDiff => {
@@ -82,7 +87,7 @@ const mergeVersions = ({
case ThreeWayDiffOutcome.CustomizedValueCanUpdate: {
return {
mergeOutcome: ThreeWayMergeOutcome.Conflict,
- mergedVersion: targetVersion,
+ mergedVersion: currentVersion,
};
}
default:
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts
new file mode 100644
index 0000000000000..a4f5197979db4
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts
@@ -0,0 +1,151 @@
+/*
+ * 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 { ThreeVersionsOf } from '../../../../../../../../common/api/detection_engine';
+import {
+ ThreeWayDiffOutcome,
+ ThreeWayMergeOutcome,
+ MissingVersion,
+} from '../../../../../../../../common/api/detection_engine';
+import { singleLineStringDiffAlgorithm } from './single_line_string_diff_algorithm';
+
+describe('singleLineStringDiffAlgorithm', () => {
+ it('returns current_version as merged output if there is no update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 'A',
+ current_version: 'A',
+ target_version: 'A',
+ };
+
+ const result = singleLineStringDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns current_version as merged output if current_version is different and there is no update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 'A',
+ current_version: 'B',
+ target_version: 'A',
+ };
+
+ const result = singleLineStringDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns target_version as merged output if current_version is the same and there is an update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 'A',
+ current_version: 'A',
+ target_version: 'B',
+ };
+
+ const result = singleLineStringDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.target_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Target,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns current_version as merged output if current version is different but it matches the update', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 'A',
+ current_version: 'B',
+ target_version: 'B',
+ };
+
+ const result = singleLineStringDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns current_version as merged output if all three versions are different', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: 'A',
+ current_version: 'B',
+ target_version: 'C',
+ };
+
+ const result = singleLineStringDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Conflict,
+ has_conflict: true,
+ })
+ );
+ });
+
+ describe('if base_version is missing', () => {
+ it('returns current_version as merged output if current_version and target_version are the same', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: MissingVersion,
+ current_version: 'A',
+ target_version: 'A',
+ };
+
+ const result = singleLineStringDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.current_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Current,
+ has_conflict: false,
+ })
+ );
+ });
+
+ it('returns target_version as merged output if current_version and target_version are different', () => {
+ const mockVersions: ThreeVersionsOf = {
+ base_version: MissingVersion,
+ current_version: 'A',
+ target_version: 'B',
+ };
+
+ const result = singleLineStringDiffAlgorithm(mockVersions);
+
+ expect(result).toEqual(
+ expect.objectContaining({
+ merged_version: mockVersions.target_version,
+ diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate,
+ merge_outcome: ThreeWayMergeOutcome.Target,
+ has_conflict: false,
+ })
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts
new file mode 100644
index 0000000000000..901bb6c050e51
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.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.
+ */
+
+import { simpleDiffAlgorithm } from './simple_diff_algorithm';
+
+export const singleLineStringDiffAlgorithm = simpleDiffAlgorithm;
From 45a73a7f294d7efb132f89d12c8affc7b931cf17 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Fri, 31 May 2024 11:10:07 -0400
Subject: [PATCH 13/46] skip failing test suite (#184585)
---
.../apps/integrations_feature_flag/endpoint_exceptions.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/security_solution_endpoint/apps/integrations_feature_flag/endpoint_exceptions.ts b/x-pack/test/security_solution_endpoint/apps/integrations_feature_flag/endpoint_exceptions.ts
index ece54cecb47f1..5a2d798109df8 100644
--- a/x-pack/test/security_solution_endpoint/apps/integrations_feature_flag/endpoint_exceptions.ts
+++ b/x-pack/test/security_solution_endpoint/apps/integrations_feature_flag/endpoint_exceptions.ts
@@ -29,7 +29,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const comboBox = getService('comboBox');
const toasts = getService('toasts');
- describe('Endpoint Exceptions', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/184585
+ describe.skip('Endpoint Exceptions', function () {
targetTags(this, ['@ess', '@serverless']);
this.timeout(10 * 60_000);
From 4eb175f384b0e079f0fdc18d55b82bdd0925b190 Mon Sep 17 00:00:00 2001
From: Coen Warmer
Date: Fri, 31 May 2024 17:12:46 +0200
Subject: [PATCH 14/46] [Observability AI Assistant] Fix Storybook (#183399)
Resolves https://github.com/elastic/kibana/issues/183400
## Summary
This fixes all broken stories in the Observability AI Assistant
storybook.
https://github.com/elastic/kibana/assets/535564/9200c47e-87bb-484d-a552-68afdf79108c
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../.storybook/preview.js | 3 +
.../components/chat/chat_body.stories.tsx | 2 +-
.../components/chat/chat_flyout.stories.tsx | 5 +-
.../chat/conversation_list.stories.tsx | 54 +++++++++++++--
.../components/chat/conversation_list.tsx | 8 +--
.../chat/function_list_popover.stories.tsx | 10 +--
.../chat/knowledge_base_callout.stories.tsx | 2 +-
.../prompt_editor/prompt_editor.stories.tsx | 69 +++++++++++++++++--
.../hooks/__storybook_mocks__/use_chat.ts | 10 +++
.../__storybook_mocks__/use_conversation.ts | 19 +++++
.../use_conversation_list.ts | 31 +++++++++
.../hooks/__storybook_mocks__/use_kibana.ts | 40 +++++++++--
.../hooks/__storybook_mocks__/use_license.ts | 18 +++++
.../use_observability_ai_assistant.ts | 8 +++
..._observability_ai_assistant_app_service.ts | 17 +++++
...observability_ai_assistant_chat_service.ts | 19 ++++-
.../use_observability_ai_assistant_router.ts | 14 ++++
...or.tsx => storybook_decorator.stories.tsx} | 32 ++++++---
18 files changed, 319 insertions(+), 42 deletions(-)
create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts
create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts
create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts
create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_license.ts
create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_app_service.ts
create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_router.ts
rename x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/{storybook_decorator.tsx => storybook_decorator.stories.tsx} (66%)
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/.storybook/preview.js b/x-pack/plugins/observability_solution/observability_ai_assistant_app/.storybook/preview.js
index 3200746243d47..5759aa5ca7f04 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/.storybook/preview.js
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/.storybook/preview.js
@@ -6,5 +6,8 @@
*/
import { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common';
+import * as jest from 'jest-mock';
export const decorators = [EuiThemeProviderDecorator];
+
+window.jest = jest;
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx
index b556617726fef..4e71ecdfd2c12 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx
@@ -8,7 +8,7 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import React from 'react';
import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
-import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
+import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { ChatBody as Component } from './chat_body';
import { buildSystemMessage } from '../../utils/builders';
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.stories.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.stories.tsx
index edae806698662..762645cb11f05 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.stories.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.stories.tsx
@@ -8,8 +8,8 @@
import { ComponentStory } from '@storybook/react';
import React from 'react';
import { buildSystemMessage } from '../../utils/builders';
-import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
-import { ChatFlyout as Component } from './chat_flyout';
+import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
+import { ChatFlyout as Component, FlyoutPositionMode } from './chat_flyout';
export default {
component: Component,
@@ -31,6 +31,7 @@ const defaultProps: ChatFlyoutProps = {
isOpen: true,
initialTitle: 'How is this working',
initialMessages: [buildSystemMessage()],
+ initialFlyoutPositionMode: FlyoutPositionMode.OVERLAY,
onClose: () => {},
};
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx
index 5d17c02d8da60..b0f72e80c5721 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx
@@ -7,7 +7,8 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import React from 'react';
-import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
+import { buildConversation } from '../../utils/builders';
+import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { ConversationList as Component } from './conversation_list';
type ConversationListProps = React.ComponentProps;
@@ -28,25 +29,64 @@ const Wrapper = (props: ConversationListProps) => {
);
};
-export const ChatHeaderLoading: ComponentStoryObj = {
- args: {},
+export const ConversationListLoading: ComponentStoryObj = {
+ args: {
+ conversations: {
+ loading: true,
+ error: undefined,
+ value: { conversations: [] },
+ refresh: () => {},
+ },
+ isLoading: true,
+ },
render: Wrapper,
};
-export const ChatHeaderError: ComponentStoryObj = {
- args: {},
+export const ConversationListError: ComponentStoryObj = {
+ args: {
+ conversations: {
+ loading: false,
+ error: new Error('Failed to load conversations'),
+ value: { conversations: [] },
+ refresh: () => {},
+ },
+ isLoading: false,
+ },
render: Wrapper,
};
-export const ChatHeaderLoaded: ComponentStoryObj = {
+export const ConversationListLoaded: ComponentStoryObj = {
args: {
+ conversations: {
+ loading: false,
+ error: undefined,
+ value: {
+ conversations: [
+ buildConversation({
+ conversation: {
+ id: 'foo',
+ title: 'Why is database service responding with errors after I did rm -rf /postgres',
+ last_updated: '',
+ },
+ }),
+ ],
+ },
+ refresh: () => {},
+ },
selectedConversationId: '',
},
render: Wrapper,
};
-export const ChatHeaderEmpty: ComponentStoryObj = {
+export const ConversationListEmpty: ComponentStoryObj = {
args: {
+ conversations: {
+ loading: false,
+ error: undefined,
+ value: { conversations: [] },
+ refresh: () => {},
+ },
+ isLoading: false,
selectedConversationId: '',
},
render: Wrapper,
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx
index 170036e834637..1b26922bcaf69 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx
@@ -46,17 +46,17 @@ const newChatButtonWrapperClassName = css`
`;
export function ConversationList({
+ conversations,
+ isLoading,
selectedConversationId,
onConversationSelect,
onConversationDeleteClick,
- conversations,
- isLoading,
}: {
+ conversations: UseConversationListResult['conversations'];
+ isLoading: boolean;
selectedConversationId?: string;
onConversationSelect?: (conversationId?: string) => void;
onConversationDeleteClick: (conversationId: string) => void;
- conversations: UseConversationListResult['conversations'];
- isLoading: boolean;
}) {
const router = useObservabilityAIAssistantRouter();
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx
index 72ad2590754e2..a8f1e23b8173d 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx
@@ -7,7 +7,7 @@
import { ComponentStory } from '@storybook/react';
import React from 'react';
-import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
+import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { FunctionListPopover as Component } from './function_list_popover';
export default {
@@ -25,9 +25,9 @@ const Template: ComponentStory = (props: FunctionListPopover)
const defaultProps: FunctionListPopover = {
onSelectFunction: () => {},
disabled: false,
- mode: 'prompt',
- selectedFunctionName: 'foo',
+ mode: 'function',
+ selectedFunctionName: '',
};
-export const ConversationList = Template.bind({});
-ConversationList.args = defaultProps;
+export const FunctionListPopover = Template.bind({});
+FunctionListPopover.args = defaultProps;
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx
index 3122631bb6561..d66729dc75a3d 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx
@@ -7,7 +7,7 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { merge } from 'lodash';
-import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
+import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { KnowledgeBaseCallout as Component } from './knowledge_base_callout';
const meta: ComponentMeta = {
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx
index 054d23b56e6de..f951653b152cc 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx
@@ -6,9 +6,10 @@
*/
import React from 'react';
-import { ComponentStory } from '@storybook/react';
+import { ComponentStory, ComponentStoryObj } from '@storybook/react';
+import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import { PromptEditor as Component, PromptEditorProps } from './prompt_editor';
-import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
+import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
/*
JSON Schema validation in the PromptEditor compponent does not work
@@ -32,7 +33,65 @@ const Template: ComponentStory = (props: PromptEditorProps) =>
return ;
};
-const defaultProps = {};
+export const PromptEditorDisabled: ComponentStoryObj = {
+ args: {
+ disabled: true,
+ hidden: false,
+ loading: false,
+ initialRole: MessageRole.User,
+ initialFunctionCall: undefined,
+ initialContent: '',
+ onChangeHeight: () => {},
+ onSendTelemetry: () => {},
+ onSubmit: () => {},
+ },
+ render: Template,
+};
+
+export const PromptEditorLoading: ComponentStoryObj = {
+ args: {
+ disabled: false,
+ hidden: false,
+ loading: true,
+ initialRole: MessageRole.User,
+ initialFunctionCall: undefined,
+ initialContent: '',
+ onChangeHeight: () => {},
+ onSendTelemetry: () => {},
+ onSubmit: () => {},
+ },
+ render: Template,
+};
-export const PromptEditor = Template.bind({});
-PromptEditor.args = defaultProps;
+export const PromptEditorWithInitialContent: ComponentStoryObj = {
+ args: {
+ disabled: false,
+ hidden: false,
+ loading: true,
+ initialRole: MessageRole.User,
+ initialFunctionCall: undefined,
+ initialContent: 'Can you help me with this?',
+ onChangeHeight: () => {},
+ onSendTelemetry: () => {},
+ onSubmit: () => {},
+ },
+ render: Template,
+};
+
+export const PromptEditorWithInitialFunction: ComponentStoryObj = {
+ args: {
+ disabled: false,
+ hidden: false,
+ loading: false,
+ initialRole: MessageRole.User,
+ initialFunctionCall: {
+ name: 'get stuff',
+ trigger: MessageRole.User,
+ },
+ initialContent: '',
+ onChangeHeight: () => {},
+ onSendTelemetry: () => {},
+ onSubmit: () => {},
+ },
+ render: Template,
+};
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts
new file mode 100644
index 0000000000000..538293f5607ad
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.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 function useChat() {
+ return { next: () => {}, messages: [], setMessages: () => {}, state: undefined, stop: () => {} };
+}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts
new file mode 100644
index 0000000000000..a6795c13cab2c
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Subject } from 'rxjs';
+
+export function useConversation() {
+ return {
+ conversation: {},
+ state: 'idle',
+ next: new Subject(),
+ stop: () => {},
+ messages: [],
+ saveTitle: () => {},
+ };
+}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts
new file mode 100644
index 0000000000000..0fc7f6d64401a
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { buildConversation } from '../../utils/builders';
+
+export function useConversationList() {
+ return {
+ deleteConversation: () => {},
+ conversations: {
+ loading: false,
+ error: undefined,
+ value: {
+ conversations: [
+ buildConversation({
+ conversation: {
+ id: 'foo',
+ title: 'Why is database service responding with errors after I did rm -rf /postgres',
+ last_updated: '',
+ },
+ }),
+ ],
+ },
+ refresh: () => {},
+ },
+ isLoading: false,
+ };
+}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts
index d9ab341dce80d..f836c3dac6159 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts
@@ -5,16 +5,16 @@
* 2.0.
*/
+import React from 'react';
+import { Subject } from 'rxjs';
+import { useChat } from './use_chat';
+
+const ObservabilityAIAssistantMultipaneFlyoutContext = React.createContext(undefined);
+
export function useKibana() {
return {
services: {
- uiSettings: {
- get: (setting: string) => {
- if (setting === 'dateFormat') {
- return 'MMM D, YYYY HH:mm';
- }
- },
- },
+ application: { navigateToApp: () => {} },
http: {
basePath: {
prepend: () => '',
@@ -26,6 +26,32 @@ export function useKibana() {
addError: () => {},
},
},
+ plugins: {
+ start: {
+ licensing: {
+ license$: new Subject(),
+ },
+ observabilityAIAssistant: {
+ useChat,
+ ObservabilityAIAssistantMultipaneFlyoutContext,
+ },
+ share: {
+ url: {
+ locators: {
+ get: () => {},
+ },
+ },
+ },
+ triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} },
+ },
+ },
+ uiSettings: {
+ get: (setting: string) => {
+ if (setting === 'dateFormat') {
+ return 'MMM D, YYYY HH:mm';
+ }
+ },
+ },
},
};
}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_license.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_license.ts
new file mode 100644
index 0000000000000..05d92d581b63f
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_license.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import useObservable from 'react-use/lib/useObservable';
+import { Subject } from 'rxjs';
+
+export function useLicense() {
+ const license = useObservable(new Subject());
+
+ return {
+ getLicense: () => license ?? null,
+ hasAtLeast: () => true,
+ };
+}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant.ts
index 978ae7671d1e5..1330ac3fa9d55 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant.ts
@@ -5,10 +5,18 @@
* 2.0.
*/
+import { Subject } from 'rxjs';
+
const service = {
start: async () => {
return {
+ chat: () => new Subject(),
+ complete: () => new Subject(),
getFunctions: [],
+ getSystemMessage: () => {},
+ hasFunction: () => true,
+ hasRenderFunction: () => true,
+ sendAnalyticsEvent: () => {},
};
},
};
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_app_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_app_service.ts
new file mode 100644
index 0000000000000..aaad808e16db5
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_app_service.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 function useObservabilityAIAssistantAppService() {
+ return {
+ conversations: {
+ predefinedConversation$: {
+ subscribe: () => {},
+ unsubscribe: () => {},
+ },
+ },
+ };
+}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_chat_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_chat_service.ts
index 35f34e8950fce..af529249ef494 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_chat_service.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_chat_service.ts
@@ -5,10 +5,27 @@
* 2.0.
*/
+import { FunctionVisibility } from '@kbn/observability-ai-assistant-plugin/public';
+import { Subject } from 'rxjs';
+
export function useObservabilityAIAssistantChatService() {
return {
+ chat: () => new Subject(),
+ complete: () => new Subject(),
getFunctions: () => {
- return [];
+ return [
+ {
+ id: 'foo',
+ name: 'foo',
+ description: 'use this function to foo',
+ descriptionForUser: 'a function that functions',
+ visibility: FunctionVisibility.All,
+ },
+ ];
},
+ getSystemMessage: () => {},
+ hasFunction: () => true,
+ hasRenderFunction: () => true,
+ sendAnalyticsEvent: () => {},
};
}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_router.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_router.ts
new file mode 100644
index 0000000000000..49b77c62c1d58
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_observability_ai_assistant_router.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 function useObservabilityAIAssistantRouter() {
+ return {
+ push: () => {},
+ replace: () => {},
+ link: () => {},
+ };
+}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx
similarity index 66%
rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.tsx
rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx
index 9651baf0d9095..9dc2e7057b951 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx
@@ -11,6 +11,8 @@ import {
createStorybookService,
type ObservabilityAIAssistantChatService,
} from '@kbn/observability-ai-assistant-plugin/public';
+import { Subject } from 'rxjs';
+import { coreMock } from '@kbn/core/public/mocks';
import { ObservabilityAIAssistantAppService } from '../service/create_app_service';
import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider';
@@ -20,21 +22,33 @@ const mockService: ObservabilityAIAssistantAppService = {
const mockChatService: ObservabilityAIAssistantChatService = createStorybookChatService();
+const coreStart = coreMock.createStart();
+
export function KibanaReactStorybookDecorator(Story: ComponentType) {
const ObservabilityAIAssistantChatServiceContext = React.createContext(mockChatService);
+ const ObservabilityAIAssistantMultipaneFlyoutContext = React.createContext({
+ container: ,
+ setVisibility: () => false,
+ });
+
return (
{
- if (setting === 'dateFormat') {
- return 'MMM D, YYYY HH:mm';
- }
- },
+ ...coreStart,
+ licensing: {
+ license$: new Subject(),
},
- observabilityAIAssistant: {
- ObservabilityAIAssistantChatServiceContext,
+ // observabilityAIAssistant: {
+ // ObservabilityAIAssistantChatServiceContext,
+ // ObservabilityAIAssistantMultipaneFlyoutContext,
+ // },
+ plugins: {
+ start: {
+ observabilityAIAssistant: {
+ ObservabilityAIAssistantMultipaneFlyoutContext,
+ },
+ triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} },
+ },
},
}}
>
From 4e8876d6a4eccb173fa9b2e35fb0cfd295555f19 Mon Sep 17 00:00:00 2001
From: Drew Tate
Date: Fri, 31 May 2024 09:20:17 -0600
Subject: [PATCH 15/46] [ES|QL] accept negated index patterns (#184528)
## Summary
`*, -.*` is now acceptable.
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
Co-authored-by: Stratoula Kalafateli
---
.../kbn-esql-validation-autocomplete/src/shared/helpers.ts | 2 +-
.../src/validation/esql_validation_meta_tests.json | 5 +++++
.../src/validation/validation.test.ts | 1 +
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
index 73f60dffb6336..348a9b45e5d12 100644
--- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
@@ -521,7 +521,7 @@ export function columnExists(
}
export function sourceExists(index: string, sources: Set) {
- if (sources.has(index)) {
+ if (sources.has(index) || index.startsWith('-')) {
return true;
}
return Boolean(fuzzySearch(index, sources.keys()));
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json
index 623d854b41b4a..d37d7974382b8 100644
--- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json
@@ -430,6 +430,11 @@
"error": [],
"warning": []
},
+ {
+ "query": "from *,-.*",
+ "error": [],
+ "warning": []
+ },
{
"query": "from indexes*",
"error": [
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
index f0c150ee47a1c..29fdf740a9dc2 100644
--- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
@@ -352,6 +352,7 @@ describe('validation logic', () => {
testErrorsAndWarnings(`from *ex*`, []);
testErrorsAndWarnings(`from in*ex`, []);
testErrorsAndWarnings(`from ind*ex`, []);
+ testErrorsAndWarnings(`from *,-.*`, []);
testErrorsAndWarnings(`from indexes*`, ['Unknown index [indexes*]']);
testErrorsAndWarnings(`from remote-*:indexes*`, []);
From aa109676fab917f2a69d743659ce4656f5ade6d6 Mon Sep 17 00:00:00 2001
From: Catherine Liu
Date: Fri, 31 May 2024 08:36:40 -0700
Subject: [PATCH 16/46] [Tests] Add test coverage for handling multiple data
views (#184460)
## Summary
Tests extracted from #180425.
This adds some smoke tests coverage for supporting multiple data views
and how dashboards/visualizations interact with the
`courier:ignoreFieldIfNotInIndex` advanced setting in Dashboard,
Controls, Lens, and Maps.
Flaky test runner x 50:
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6160
### Checklist
Delete any items that are not applicable to this PR.
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
### Risk Matrix
Delete this section if it is not applicable to this PR.
Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.
When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:
| Risk | Probability | Severity | Mitigation/Notes |
|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces—unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes—Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
.../functional/apps/dashboard/group2/index.ts | 1 +
.../dashboard/group2/multiple_data_views.ts | 69 +++++++
.../controls/common/index.ts | 1 +
.../controls/common/multiple_data_views.ts | 188 ++++++++++++++++++
.../test/functional/apps/lens/group1/index.ts | 1 +
.../apps/lens/group1/multiple_data_views.ts | 116 +++++++++++
.../test/functional/apps/maps/group2/index.js | 1 +
.../apps/maps/group2/multiple_data_views.ts | 100 ++++++++++
8 files changed, 477 insertions(+)
create mode 100644 test/functional/apps/dashboard/group2/multiple_data_views.ts
create mode 100644 test/functional/apps/dashboard_elements/controls/common/multiple_data_views.ts
create mode 100644 x-pack/test/functional/apps/lens/group1/multiple_data_views.ts
create mode 100644 x-pack/test/functional/apps/maps/group2/multiple_data_views.ts
diff --git a/test/functional/apps/dashboard/group2/index.ts b/test/functional/apps/dashboard/group2/index.ts
index 6e967851c05ed..171f8d8d72d39 100644
--- a/test/functional/apps/dashboard/group2/index.ts
+++ b/test/functional/apps/dashboard/group2/index.ts
@@ -30,5 +30,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./dashboard_filter_bar'));
loadTestFile(require.resolve('./dashboard_filtering'));
loadTestFile(require.resolve('./panel_expand_toggle'));
+ loadTestFile(require.resolve('./multiple_data_views'));
});
}
diff --git a/test/functional/apps/dashboard/group2/multiple_data_views.ts b/test/functional/apps/dashboard/group2/multiple_data_views.ts
new file mode 100644
index 0000000000000..0e4e04d47d0ea
--- /dev/null
+++ b/test/functional/apps/dashboard/group2/multiple_data_views.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+/**
+ * Test the filtering behavior of a dashboard with multiple data views
+ */
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const dashboardAddPanel = getService('dashboardAddPanel');
+ const testSubjects = getService('testSubjects');
+ const filterBar = getService('filterBar');
+ const kibanaServer = getService('kibanaServer');
+ const PageObjects = getPageObjects(['common', 'dashboard', 'timePicker', 'home']);
+
+ describe('dashboard multiple data views', () => {
+ before(async () => {
+ await kibanaServer.uiSettings.update({ 'courier:ignoreFilterIfFieldNotInIndex': true });
+ await PageObjects.common.navigateToApp('home');
+ await PageObjects.home.goToSampleDataPage();
+ await PageObjects.home.addSampleDataSet('flights');
+ await PageObjects.home.addSampleDataSet('logs');
+ await PageObjects.dashboard.navigateToApp();
+ await PageObjects.dashboard.gotoDashboardLandingPage();
+ await PageObjects.dashboard.clickNewDashboard();
+ await dashboardAddPanel.addSavedSearches(['[Flights] Flight Log', '[Logs] Visits']);
+ await PageObjects.dashboard.waitForRenderComplete();
+ await PageObjects.timePicker.setCommonlyUsedTime('This_week');
+ });
+
+ after(async () => {
+ await PageObjects.common.navigateToApp('home');
+ await PageObjects.home.goToSampleDataPage();
+ await PageObjects.home.removeSampleDataSet('flights');
+ await PageObjects.home.removeSampleDataSet('logs');
+ await kibanaServer.uiSettings.unset('courier:ignoreFilterIfFieldNotInIndex');
+ });
+
+ it('ignores filters on panels using a data view without the filter field', async () => {
+ await filterBar.addFilter({ field: 'Carrier', operation: 'exists' });
+ const logsSavedSearchPanel = (await testSubjects.findAll('embeddedSavedSearchDocTable'))[1];
+ expect(
+ await (
+ await logsSavedSearchPanel.findByCssSelector('[data-document-number]')
+ ).getAttribute('data-document-number')
+ ).to.not.be('0');
+ });
+
+ it('applies filters on panels using a data view without the filter field', async () => {
+ await kibanaServer.uiSettings.update({ 'courier:ignoreFilterIfFieldNotInIndex': false });
+ await PageObjects.dashboard.navigateToApp();
+ await testSubjects.click('edit-unsaved-New-Dashboard');
+ await PageObjects.dashboard.waitForRenderComplete();
+ const logsSavedSearchPanel = (await testSubjects.findAll('embeddedSavedSearchDocTable'))[1];
+ expect(
+ await (
+ await logsSavedSearchPanel.findByCssSelector('[data-document-number]')
+ ).getAttribute('data-document-number')
+ ).to.be('0');
+ });
+ });
+}
diff --git a/test/functional/apps/dashboard_elements/controls/common/index.ts b/test/functional/apps/dashboard_elements/controls/common/index.ts
index c9098b1a2c688..c5fb1621e61f3 100644
--- a/test/functional/apps/dashboard_elements/controls/common/index.ts
+++ b/test/functional/apps/dashboard_elements/controls/common/index.ts
@@ -46,5 +46,6 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid
loadTestFile(require.resolve('./control_group_chaining'));
loadTestFile(require.resolve('./control_group_apply_button'));
loadTestFile(require.resolve('./replace_controls'));
+ loadTestFile(require.resolve('./multiple_data_views'));
});
}
diff --git a/test/functional/apps/dashboard_elements/controls/common/multiple_data_views.ts b/test/functional/apps/dashboard_elements/controls/common/multiple_data_views.ts
new file mode 100644
index 0000000000000..45b0829f6ca5f
--- /dev/null
+++ b/test/functional/apps/dashboard_elements/controls/common/multiple_data_views.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 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 { OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL } from '@kbn/controls-plugin/common';
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const security = getService('security');
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const filterBar = getService('filterBar');
+ const testSubjects = getService('testSubjects');
+ const dashboardAddPanel = getService('dashboardAddPanel');
+ const { common, dashboard, dashboardControls } = getPageObjects([
+ 'dashboardControls',
+ 'dashboard',
+ 'console',
+ 'common',
+ 'header',
+ ]);
+
+ describe('Dashboard control group with multiple data views', () => {
+ let controlIds: string[];
+
+ before(async () => {
+ await security.testUser.setRoles(['kibana_admin', 'kibana_sample_admin']);
+
+ await kibanaServer.importExport.load(
+ 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana'
+ );
+ await esArchiver.load('test/functional/fixtures/es_archiver/kibana_sample_data_flights');
+ await kibanaServer.importExport.load(
+ 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern'
+ );
+ await kibanaServer.uiSettings.replace({
+ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c',
+ 'courier:ignoreFilterIfFieldNotInIndex': true,
+ });
+
+ await common.setTime({
+ from: 'Apr 10, 2018 @ 00:00:00.000',
+ to: 'Nov 15, 2018 @ 00:00:00.000',
+ });
+
+ await dashboard.navigateToApp();
+ await dashboard.clickNewDashboard();
+
+ await dashboardControls.createControl({
+ controlType: OPTIONS_LIST_CONTROL,
+ dataViewTitle: 'kibana_sample_data_flights',
+ fieldName: 'Carrier',
+ title: 'Carrier',
+ });
+
+ await dashboardControls.createControl({
+ controlType: RANGE_SLIDER_CONTROL,
+ dataViewTitle: 'kibana_sample_data_flights',
+ fieldName: 'AvgTicketPrice',
+ title: 'Average Ticket Price',
+ });
+
+ await dashboardControls.createControl({
+ controlType: OPTIONS_LIST_CONTROL,
+ dataViewTitle: 'logstash-*',
+ fieldName: 'machine.os.raw',
+ title: 'Operating System',
+ });
+
+ await dashboardControls.createControl({
+ controlType: RANGE_SLIDER_CONTROL,
+ dataViewTitle: 'logstash-*',
+ fieldName: 'bytes',
+ title: 'Bytes',
+ });
+
+ await dashboardAddPanel.addSavedSearch('logstash hits');
+
+ controlIds = await dashboardControls.getAllControlIds();
+ });
+
+ after(async () => {
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana'
+ );
+ await esArchiver.unload('test/functional/fixtures/es_archiver/kibana_sample_data_flights');
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern'
+ );
+ await security.testUser.restoreDefaults();
+ await kibanaServer.uiSettings.unset('courier:ignoreFilterIfFieldNotInIndex');
+ await kibanaServer.uiSettings.unset('defaultIndex');
+ });
+
+ it('ignores global filters on controls using a data view without the filter field', async () => {
+ await filterBar.addFilter({ field: 'Carrier', operation: 'exists' });
+
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ expect(await dashboardControls.optionsListGetCardinalityValue()).to.be('4');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]);
+
+ dashboardControls.validateRange('placeholder', controlIds[1], '100', '1200');
+
+ await dashboardControls.optionsListOpenPopover(controlIds[2]);
+ expect(await dashboardControls.optionsListGetCardinalityValue()).to.be('5');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[2]);
+
+ dashboardControls.validateRange('placeholder', controlIds[3], '0', '19979');
+ });
+
+ it('ignores controls on other controls and panels using a data view without the control field by default', async () => {
+ await filterBar.removeFilter('Carrier');
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ await dashboardControls.optionsListPopoverSelectOption('Kibana Airlines');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]);
+
+ dashboardControls.validateRange('placeholder', controlIds[1], '100', '1196');
+
+ await dashboardControls.optionsListOpenPopover(controlIds[2]);
+ expect(await dashboardControls.optionsListGetCardinalityValue()).to.be('5');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[2]);
+
+ dashboardControls.validateRange('placeholder', controlIds[3], '0', '19979');
+
+ const logstashSavedSearchPanel = await testSubjects.find('embeddedSavedSearchDocTable');
+ expect(
+ await (
+ await logstashSavedSearchPanel.findByCssSelector('[data-document-number]')
+ ).getAttribute('data-document-number')
+ ).to.not.be('0');
+ });
+
+ it('applies global filters on controls using data view a without the filter field', async () => {
+ await kibanaServer.uiSettings.update({ 'courier:ignoreFilterIfFieldNotInIndex': false });
+ await common.navigateToApp('dashboard');
+ await testSubjects.click('edit-unsaved-New-Dashboard');
+ await filterBar.addFilter({ field: 'Carrier', operation: 'exists' });
+
+ await Promise.all([
+ dashboardControls.optionsListWaitForLoading(controlIds[0]),
+ dashboardControls.rangeSliderWaitForLoading(controlIds[1]),
+ dashboardControls.optionsListWaitForLoading(controlIds[2]),
+ dashboardControls.rangeSliderWaitForLoading(controlIds[3]),
+ ]);
+
+ await dashboardControls.clearControlSelections(controlIds[0]);
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ expect(await dashboardControls.optionsListGetCardinalityValue()).to.be('4');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]);
+
+ dashboardControls.validateRange('placeholder', controlIds[1], '100', '1200');
+
+ await dashboardControls.optionsListOpenPopover(controlIds[2]);
+ expect(await dashboardControls.optionsListGetCardinalityValue()).to.be('0');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[2]);
+
+ dashboardControls.validateRange('placeholder', controlIds[3], '0', '0');
+ });
+
+ it('applies global filters on controls using a data view without the filter field', async () => {
+ await filterBar.removeFilter('Carrier');
+ await dashboardControls.optionsListOpenPopover(controlIds[0]);
+ await dashboardControls.optionsListPopoverSelectOption('Kibana Airlines');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]);
+
+ dashboardControls.validateRange('placeholder', controlIds[1], '100', '1196');
+
+ await dashboardControls.optionsListOpenPopover(controlIds[2]);
+ expect(await dashboardControls.optionsListGetCardinalityValue()).to.be('0');
+ await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[2]);
+
+ dashboardControls.validateRange('placeholder', controlIds[3], '0', '0');
+
+ const logstashSavedSearchPanel = await testSubjects.find('embeddedSavedSearchDocTable');
+ expect(
+ await (
+ await logstashSavedSearchPanel.findByCssSelector('[data-document-number]')
+ ).getAttribute('data-document-number')
+ ).to.be('0');
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/lens/group1/index.ts b/x-pack/test/functional/apps/lens/group1/index.ts
index 67d4e8023e4fc..1f02923816c64 100644
--- a/x-pack/test/functional/apps/lens/group1/index.ts
+++ b/x-pack/test/functional/apps/lens/group1/index.ts
@@ -77,6 +77,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
// total run time ~16 min
loadTestFile(require.resolve('./smokescreen')); // 12m 12s
loadTestFile(require.resolve('./ad_hoc_data_view')); // 3m 40s
+ loadTestFile(require.resolve('./multiple_data_views'));
}
});
};
diff --git a/x-pack/test/functional/apps/lens/group1/multiple_data_views.ts b/x-pack/test/functional/apps/lens/group1/multiple_data_views.ts
new file mode 100644
index 0000000000000..75d3d7ca6caf1
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group1/multiple_data_views.ts
@@ -0,0 +1,116 @@
+/*
+ * 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 { DebugState } from '@elastic/charts';
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const PageObjects = getPageObjects(['common', 'visualize', 'lens']);
+ const filterBar = getService('filterBar');
+ const elasticChart = getService('elasticChart');
+ const testSubjects = getService('testSubjects');
+ const kibanaServer = getService('kibanaServer');
+ const esArchiver = getService('esArchiver');
+
+ const expectedLogstashData = [
+ { x: 1540278360000, y: 4735 },
+ { x: 1540280820000, y: 2836 },
+ ];
+ const expectedFlightsData = [
+ { x: 1540278720000, y: 12993.16 },
+ { x: 1540279080000, y: 7927.47 },
+ { x: 1540279500000, y: 7548.66 },
+ { x: 1540280400000, y: 8418.08 },
+ { x: 1540280580000, y: 11577.86 },
+ { x: 1540281060000, y: 8088.12 },
+ { x: 1540281240000, y: 6943.55 },
+ ];
+
+ function assertMatchesExpectedData(
+ state: DebugState,
+ expectedData: Array>
+ ) {
+ expect(
+ state?.bars?.map(({ bars }) =>
+ bars.map((bar) => ({
+ x: bar.x,
+ y: Math.floor(bar.y * 100) / 100,
+ }))
+ )
+ ).to.eql(expectedData);
+ }
+
+ describe('lens with multiple data views', () => {
+ const visTitle = 'xyChart with multiple data views';
+
+ before(async () => {
+ await PageObjects.common.setTime({
+ from: 'Oct 23, 2018 @ 07:00:00.000',
+ to: 'Oct 23, 2018 @ 08:00:00.000',
+ });
+
+ await kibanaServer.uiSettings.update({ 'courier:ignoreFilterIfFieldNotInIndex': true });
+
+ await esArchiver.load('test/functional/fixtures/es_archiver/kibana_sample_data_flights');
+ await kibanaServer.importExport.load(
+ 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern'
+ );
+
+ await esArchiver.load('test/functional/fixtures/es_archiver/long_window_logstash');
+ await kibanaServer.importExport.load(
+ 'test/functional/fixtures/kbn_archiver/long_window_logstash_index_pattern'
+ );
+ await PageObjects.common.navigateToApp('lens');
+ });
+
+ after(async () => {
+ await kibanaServer.uiSettings.unset('courier:ignoreFilterIfFieldNotInIndex');
+ await esArchiver.unload('test/functional/fixtures/es_archiver/kibana_sample_data_flights');
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern'
+ );
+ await esArchiver.unload('test/functional/fixtures/es_archiver/long_window_logstash');
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/long_window_logstash_index_pattern'
+ );
+ });
+
+ it('should allow building a chart with multiple data views', async () => {
+ await elasticChart.setNewChartUiDebugFlag(true);
+
+ // Logstash layer
+ await PageObjects.lens.switchDataPanelIndexPattern('long-window-logstash-*');
+ await testSubjects.click('fieldToggle-bytes');
+
+ // Flights layer
+ await PageObjects.lens.switchDataPanelIndexPattern('kibana_sample_data_flights');
+ await PageObjects.lens.createLayer('data');
+ await testSubjects.click('fieldToggle-DistanceKilometers');
+
+ const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
+ assertMatchesExpectedData(data, [expectedLogstashData, expectedFlightsData]);
+ });
+
+ it('ignores global filters on layers using a data view without the filter field', async () => {
+ await filterBar.addFilter({ field: 'Carrier', operation: 'exists' });
+ const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
+ assertMatchesExpectedData(data, [expectedLogstashData, expectedFlightsData]);
+ await PageObjects.lens.save(visTitle);
+ });
+
+ it('applies global filters on layers using data view a without the filter field', async () => {
+ await kibanaServer.uiSettings.update({ 'courier:ignoreFilterIfFieldNotInIndex': false });
+ await PageObjects.common.navigateToApp('visualize');
+ await elasticChart.setNewChartUiDebugFlag(true);
+
+ await PageObjects.visualize.openSavedVisualization(visTitle);
+ const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
+ assertMatchesExpectedData(data, [expectedFlightsData]);
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/maps/group2/index.js b/x-pack/test/functional/apps/maps/group2/index.js
index 884c7a500c446..c2130f4587041 100644
--- a/x-pack/test/functional/apps/maps/group2/index.js
+++ b/x-pack/test/functional/apps/maps/group2/index.js
@@ -61,5 +61,6 @@ export default function ({ loadTestFile, getService }) {
loadTestFile(require.resolve('./es_geo_grid_source'));
loadTestFile(require.resolve('./adhoc_data_view'));
loadTestFile(require.resolve('./embeddable'));
+ loadTestFile(require.resolve('./multiple_data_views'));
});
}
diff --git a/x-pack/test/functional/apps/maps/group2/multiple_data_views.ts b/x-pack/test/functional/apps/maps/group2/multiple_data_views.ts
new file mode 100644
index 0000000000000..facc97a0b46cd
--- /dev/null
+++ b/x-pack/test/functional/apps/maps/group2/multiple_data_views.ts
@@ -0,0 +1,100 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getPageObjects, getService }: FtrProviderContext) {
+ const kibanaServer = getService('kibanaServer');
+ const esArchiver = getService('esArchiver');
+ const filterBar = getService('filterBar');
+ const testSubjects = getService('testSubjects');
+ const PageObjects = getPageObjects(['common', 'maps']);
+
+ describe('maps with multiple data views', () => {
+ const mapTitle = 'map with multiple data views';
+
+ const createLayerForDataView = async (dataView: string) => {
+ await PageObjects.maps.clickAddLayer();
+ await testSubjects.click('documents');
+ await PageObjects.maps.selectGeoIndexPatternLayer(dataView);
+ await testSubjects.click('importFileButton');
+ await testSubjects.click('layerPanelCancelButton');
+ };
+
+ before(async () => {
+ await PageObjects.common.setTime({
+ from: 'Oct 23, 2018 @ 07:00:00.000',
+ to: 'Oct 23, 2018 @ 08:00:00.000',
+ });
+
+ await esArchiver.load('test/functional/fixtures/es_archiver/kibana_sample_data_flights');
+ await kibanaServer.importExport.load(
+ 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern'
+ );
+
+ await esArchiver.load('test/functional/fixtures/es_archiver/long_window_logstash');
+ await kibanaServer.importExport.load(
+ 'test/functional/fixtures/kbn_archiver/long_window_logstash_index_pattern'
+ );
+ await kibanaServer.uiSettings.update({ 'courier:ignoreFilterIfFieldNotInIndex': true });
+
+ await PageObjects.maps.openNewMap();
+ });
+
+ after(async () => {
+ await kibanaServer.uiSettings.unset('courier:ignoreFilterIfFieldNotInIndex');
+ await esArchiver.unload('test/functional/fixtures/es_archiver/kibana_sample_data_flights');
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern'
+ );
+ await esArchiver.unload('test/functional/fixtures/es_archiver/long_window_logstash');
+ await kibanaServer.importExport.unload(
+ 'test/functional/fixtures/kbn_archiver/long_window_logstash_index_pattern'
+ );
+ });
+
+ it('should allow building a map with multiple data views', async () => {
+ // Logstash layer
+ await createLayerForDataView('long-window-logstash-*');
+
+ // Flights layer
+ await createLayerForDataView('kibana_sample_data_flights');
+
+ expect(await PageObjects.maps.getNumberOfLayers()).to.be(3);
+ expect(await PageObjects.maps.getLayerTocTooltipMsg('kibana_sample_data_flights')).to.equal(
+ 'kibana_sample_data_flights\nFound 9 documents.\nResults narrowed by global time'
+ );
+ expect(await PageObjects.maps.getLayerTocTooltipMsg('long-window-logstash-*')).to.equal(
+ 'long-window-logstash-*\nFound 2 documents.\nResults narrowed by global time'
+ );
+ });
+
+ it('ignores global filters on layers using a data view without the filter field by default', async () => {
+ await filterBar.addFilter({ field: '@message', operation: 'exists' });
+ expect(await PageObjects.maps.getLayerTocTooltipMsg('kibana_sample_data_flights')).to.equal(
+ 'kibana_sample_data_flights\nFound 9 documents.\nResults narrowed by global search\nResults narrowed by global time'
+ );
+ expect(await PageObjects.maps.getLayerTocTooltipMsg('long-window-logstash-*')).to.equal(
+ 'long-window-logstash-*\nFound 2 documents.\nResults narrowed by global search\nResults narrowed by global time'
+ );
+
+ await PageObjects.maps.saveMap(mapTitle);
+ });
+
+ it('applies global filters on layers using data view a without the filter field', async () => {
+ await kibanaServer.uiSettings.update({ 'courier:ignoreFilterIfFieldNotInIndex': false });
+ await PageObjects.maps.loadSavedMap(mapTitle);
+ expect(await PageObjects.maps.getLayerTocTooltipMsg('kibana_sample_data_flights')).to.equal(
+ 'kibana_sample_data_flights\nNo results found.\nResults narrowed by global search\nResults narrowed by global time'
+ );
+ expect(await PageObjects.maps.getLayerTocTooltipMsg('long-window-logstash-*')).to.equal(
+ 'long-window-logstash-*\nFound 2 documents.\nResults narrowed by global search\nResults narrowed by global time'
+ );
+ });
+ });
+}
From d9aa9893c6ed6b987ef3599f15a6578512612057 Mon Sep 17 00:00:00 2001
From: Steph Milovic
Date: Fri, 31 May 2024 09:39:54 -0600
Subject: [PATCH 17/46] Connector token client fixes (#184550)
---
.../server/lib/connector_token_client.test.ts | 63 ++++++++++++-------
.../server/lib/connector_token_client.ts | 34 +++++-----
.../server/lib/retry_if_conflicts.test.ts | 57 +++++++++++++++++
.../actions/server/lib/retry_if_conflicts.ts | 55 ++++++++++++++++
4 files changed, 167 insertions(+), 42 deletions(-)
create mode 100644 x-pack/plugins/actions/server/lib/retry_if_conflicts.test.ts
create mode 100644 x-pack/plugins/actions/server/lib/retry_if_conflicts.ts
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 d1308537400b4..baedd2ff07beb 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
@@ -11,6 +11,7 @@ import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/s
import { ConnectorTokenClient } from './connector_token_client';
import { Logger } from '@kbn/core/server';
import { ConnectorToken } from '../types';
+import * as allRetry from './retry_if_conflicts';
const logger = loggingSystemMock.create().get() as jest.Mocked;
jest.mock('@kbn/core-saved-objects-utils-server', () => {
@@ -301,30 +302,47 @@ describe('update()', () => {
},
references: [],
});
- unsecuredSavedObjectsClient.checkConflicts.mockResolvedValueOnce({
- errors: [
- {
- id: '1',
- error: {
- error: 'error',
- statusCode: 503,
- message: 'There is a conflict.',
- },
- type: 'conflict',
- },
- ],
- });
+ const retryIfConflictsMock = jest.spyOn(allRetry, 'retryIfConflicts');
+ retryIfConflictsMock.mockRejectedValue(new Error('There is a conflict.'));
+ await expect(
+ connectorTokenClient.update({
+ id: '1',
+ tokenType: 'access_token',
+ token: 'testtokenvalue',
+ expiresAtMillis: expiresAt,
+ })
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`"There is a conflict."`);
+ expect(logger.error.mock.calls[0]).toMatchObject([
+ 'Failed to update connector_token for id "1" and tokenType: "access_token". Error: There is a conflict.',
+ ]);
+ });
- const result = await connectorTokenClient.update({
+ test('should attempt oper', async () => {
+ const expiresAt = new Date().toISOString();
+
+ unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
id: '1',
- tokenType: 'access_token',
- token: 'testtokenvalue',
- expiresAtMillis: expiresAt,
+ type: 'connector_token',
+ attributes: {
+ connectorId: '123',
+ tokenType: 'access_token',
+ token: 'testtokenvalue',
+ createdAt: new Date().toISOString(),
+ },
+ references: [],
});
- expect(result).toEqual(null);
- expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(0);
+ const retryIfConflictsMock = jest.spyOn(allRetry, 'retryIfConflicts');
+ retryIfConflictsMock.mockRejectedValue(new Error('There is a conflict.'));
+ await expect(
+ connectorTokenClient.update({
+ id: '1',
+ tokenType: 'access_token',
+ token: 'testtokenvalue',
+ expiresAtMillis: expiresAt,
+ })
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`"There is a conflict."`);
expect(logger.error.mock.calls[0]).toMatchObject([
- 'Failed to update connector_token for id "1" and tokenType: "access_token". Error: There is a conflict. ',
+ 'Failed to update connector_token for id "1" and tokenType: "access_token". Error: There is a conflict.',
]);
});
@@ -560,9 +578,7 @@ describe('updateOrReplace()', () => {
},
references: [],
});
- unsecuredSavedObjectsClient.checkConflicts.mockResolvedValueOnce({
- errors: [],
- });
+
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: '1',
type: 'connector_token',
@@ -594,7 +610,6 @@ describe('updateOrReplace()', () => {
expect(unsecuredSavedObjectsClient.delete).not.toHaveBeenCalled();
expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1);
- expect(unsecuredSavedObjectsClient.checkConflicts).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1);
expect((unsecuredSavedObjectsClient.create.mock.calls[0][1] as ConnectorToken).token).toBe(
'newToken'
diff --git a/x-pack/plugins/actions/server/lib/connector_token_client.ts b/x-pack/plugins/actions/server/lib/connector_token_client.ts
index 79febc6f79dc8..cf9ae95658859 100644
--- a/x-pack/plugins/actions/server/lib/connector_token_client.ts
+++ b/x-pack/plugins/actions/server/lib/connector_token_client.ts
@@ -8,10 +8,12 @@
import { omitBy, isUndefined } from 'lodash';
import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
import { Logger, SavedObjectsClientContract, SavedObjectsUtils } from '@kbn/core/server';
+import { retryIfConflicts } from './retry_if_conflicts';
import { ConnectorToken } from '../types';
import { CONNECTOR_TOKEN_SAVED_OBJECT_TYPE } from '../constants/saved_objects';
export const MAX_TOKENS_RETURNED = 1;
+const MAX_RETRY_ATTEMPTS = 3;
interface ConstructorOptions {
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
@@ -107,22 +109,10 @@ export class ConnectorTokenClient {
id
);
const createTime = Date.now();
- const conflicts = await this.unsecuredSavedObjectsClient.checkConflicts([
- { id, type: 'connector_token' },
- ]);
+
try {
- if (conflicts.errors.length > 0) {
- this.logger.error(
- `Failed to update connector_token for id "${id}" and tokenType: "${
- tokenType ?? 'access_token'
- }". ${conflicts.errors.reduce(
- (messages, errorObj) => `Error: ${errorObj.error.message} ${messages}`,
- ''
- )}`
- );
- return null;
- } else {
- const result = await this.unsecuredSavedObjectsClient.create(
+ const updateOperation = () => {
+ return this.unsecuredSavedObjectsClient.create(
CONNECTOR_TOKEN_SAVED_OBJECT_TYPE,
{
...attributes,
@@ -141,8 +131,16 @@ export class ConnectorTokenClient {
isUndefined
)
);
- return result.attributes as ConnectorToken;
- }
+ };
+
+ const result = await retryIfConflicts(
+ this.logger,
+ `accessToken.create('${id}')`,
+ updateOperation,
+ MAX_RETRY_ATTEMPTS
+ );
+
+ return result.attributes as ConnectorToken;
} catch (err) {
this.logger.error(
`Failed to update connector_token for id "${id}" and tokenType: "${
@@ -178,7 +176,7 @@ export class ConnectorTokenClient {
perPage: MAX_TOKENS_RETURNED,
type: CONNECTOR_TOKEN_SAVED_OBJECT_TYPE,
filter: `${CONNECTOR_TOKEN_SAVED_OBJECT_TYPE}.attributes.connectorId: "${connectorId}"${tokenTypeFilter}`,
- sortField: 'updatedAt',
+ sortField: 'updated_at',
sortOrder: 'desc',
})
).saved_objects
diff --git a/x-pack/plugins/actions/server/lib/retry_if_conflicts.test.ts b/x-pack/plugins/actions/server/lib/retry_if_conflicts.test.ts
new file mode 100644
index 0000000000000..200077e24c2c8
--- /dev/null
+++ b/x-pack/plugins/actions/server/lib/retry_if_conflicts.test.ts
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { Logger, SavedObjectsErrorHelpers } from '@kbn/core/server';
+import { retryIfConflicts, RetryForConflictsAttempts } from './retry_if_conflicts';
+import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
+
+jest.mock('@kbn/core/server');
+
+const mockLogger = loggingSystemMock.create().get() as jest.Mocked;
+
+describe('retryIfConflicts', () => {
+ let logger: Logger;
+
+ beforeEach(() => {
+ logger = mockLogger;
+ (SavedObjectsErrorHelpers.isConflictError as jest.Mock).mockReturnValue(true);
+ });
+
+ it('should execute operation successfully without conflicts', async () => {
+ const operation = jest.fn().mockResolvedValue('success');
+ const result = await retryIfConflicts(logger, 'testOperation', operation);
+ expect(result).toBe('success');
+ expect(operation).toHaveBeenCalledTimes(1);
+ });
+
+ it('should retry the operation on conflict error', async () => {
+ const operation = jest.fn().mockRejectedValueOnce('conflict').mockResolvedValueOnce('success');
+
+ const result = await retryIfConflicts(logger, 'testOperation', operation);
+ expect(result).toBe('success');
+ expect(operation).toHaveBeenCalledTimes(2);
+ expect(logger.debug).toHaveBeenCalledWith('testOperation conflict, retrying ...');
+ });
+
+ it('should throw error if maximum retries exceeded', async () => {
+ const operation = jest.fn().mockRejectedValue('conflict');
+
+ await expect(retryIfConflicts(logger, 'testOperation', operation)).rejects.toBe('conflict');
+ expect(operation).toHaveBeenCalledTimes(RetryForConflictsAttempts + 1);
+ expect(logger.warn).toHaveBeenCalledWith('testOperation conflict, exceeded retries');
+ });
+
+ it('should throw non-conflict error immediately', async () => {
+ (SavedObjectsErrorHelpers.isConflictError as jest.Mock).mockReturnValue(false);
+ const nonConflictError = new Error('non-conflict error');
+ const operation = jest.fn().mockRejectedValue(nonConflictError);
+
+ await expect(retryIfConflicts(logger, 'testOperation', operation)).rejects.toThrow(
+ nonConflictError
+ );
+ expect(operation).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/x-pack/plugins/actions/server/lib/retry_if_conflicts.ts b/x-pack/plugins/actions/server/lib/retry_if_conflicts.ts
new file mode 100644
index 0000000000000..4778e1ced1013
--- /dev/null
+++ b/x-pack/plugins/actions/server/lib/retry_if_conflicts.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.
+ */
+
+// This module provides a helper to perform retries on a function if the
+// function ends up throwing a SavedObject 409 conflict.
+// This is a copy of the retryIfConflicts function from the alerting plugin
+
+import { Logger, SavedObjectsErrorHelpers } from '@kbn/core/server';
+
+type RetryableForConflicts = () => Promise;
+
+// number of times to retry when conflicts occur
+export const RetryForConflictsAttempts = 2;
+
+// milliseconds to wait before retrying when conflicts occur
+// note: we considered making this random, to help avoid a stampede, but
+// with 1 retry it probably doesn't matter, and adding randomness could
+// make it harder to diagnose issues
+const RetryForConflictsDelay = 250;
+
+// retry an operation if it runs into 409 Conflict's, up to a limit
+export async function retryIfConflicts(
+ logger: Logger,
+ name: string,
+ operation: RetryableForConflicts,
+ retries: number = RetryForConflictsAttempts
+): Promise {
+ // run the operation, return if no errors or throw if not a conflict error
+ try {
+ return await operation();
+ } catch (err) {
+ if (!SavedObjectsErrorHelpers.isConflictError(err)) {
+ throw err;
+ }
+
+ // must be a conflict; if no retries left, throw it
+ if (retries <= 0) {
+ logger.warn(`${name} conflict, exceeded retries`);
+ throw err;
+ }
+
+ // delay a bit before retrying
+ logger.debug(`${name} conflict, retrying ...`);
+ await waitBeforeNextRetry();
+ return await retryIfConflicts(logger, name, operation, retries - 1);
+ }
+}
+
+async function waitBeforeNextRetry(): Promise {
+ await new Promise((resolve) => setTimeout(resolve, RetryForConflictsDelay));
+}
From cc317a0813283f145d1d5eb5ff85091620bad8b3 Mon Sep 17 00:00:00 2001
From: Chris Cowan
Date: Fri, 31 May 2024 10:00:38 -0600
Subject: [PATCH 18/46] [Entities] Add `spaceId` to entities (#183943)
## Summary
This PR closes https://github.com/elastic/observed-asset-model/issues/67
by capturing the `spaceId` from the API request and storing the
`entity.spaceId` via the ingest pipeline.
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../kbn-entities-schema/src/schema/entity.ts | 1 +
.../asset_manager/kibana.jsonc | 1 +
.../create_and_install_ingest_pipeline.ts | 5 ++--
.../generate_processors.test.ts.snap | 6 +++++
.../generate_processors.test.ts | 3 ++-
.../ingest_pipeline/generate_processors.ts | 8 +++++-
.../asset_manager/server/plugin.ts | 7 ++++-
.../server/routes/entities/create.ts | 10 ++++---
.../server/routes/entities/reset.ts | 4 ++-
.../asset_manager/server/routes/index.ts | 26 ++++++++-----------
.../asset_manager/server/routes/types.ts | 2 ++
.../asset_manager/server/types.ts | 2 ++
.../asset_manager/tsconfig.json | 3 ++-
13 files changed, 53 insertions(+), 25 deletions(-)
diff --git a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts
index 514ed01494036..cf4be826acf04 100644
--- a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts
+++ b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts
@@ -15,6 +15,7 @@ export const entitySchema = z.intersection(
indexPatterns: arrayOfStringsSchema,
identityFields: arrayOfStringsSchema,
metric: z.record(z.string(), z.number()),
+ spaceId: z.string(),
}),
}),
z.record(z.string(), z.string().or(z.number()))
diff --git a/x-pack/plugins/observability_solution/asset_manager/kibana.jsonc b/x-pack/plugins/observability_solution/asset_manager/kibana.jsonc
index 1ee4b12d55ea7..86c6db40359de 100644
--- a/x-pack/plugins/observability_solution/asset_manager/kibana.jsonc
+++ b/x-pack/plugins/observability_solution/asset_manager/kibana.jsonc
@@ -10,6 +10,7 @@
"assetManager"
],
"optionalPlugins": [
+ "spaces"
],
"requiredPlugins": [
"apmDataAccess",
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/create_and_install_ingest_pipeline.ts b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/create_and_install_ingest_pipeline.ts
index 74c4bb7f79358..cb820d14de487 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/create_and_install_ingest_pipeline.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/create_and_install_ingest_pipeline.ts
@@ -15,9 +15,10 @@ import { generateIngestPipelineId } from './ingest_pipeline/generate_ingest_pipe
export async function createAndInstallIngestPipeline(
esClient: ElasticsearchClient,
definition: EntityDefinition,
- logger: Logger
+ logger: Logger,
+ spaceId: string
) {
- const processors = generateProcessors(definition);
+ const processors = generateProcessors(definition, spaceId);
const id = generateIngestPipelineId(definition);
try {
await retryTransientEsErrors(
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_processors.test.ts.snap b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_processors.test.ts.snap
index 443063d70db60..43bae00156a4c 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_processors.test.ts.snap
+++ b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_processors.test.ts.snap
@@ -8,6 +8,12 @@ Array [
"value": "{{{_ingest.timestamp}}}",
},
},
+ Object {
+ "set": Object {
+ "field": "entity.spaceId",
+ "value": "test",
+ },
+ },
Object {
"set": Object {
"field": "entity.definitionId",
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.test.ts b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.test.ts
index 33919ec678dcf..a2eb9c3ecb6f7 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.test.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.test.ts
@@ -10,7 +10,8 @@ import { entityDefinition } from '../helpers/fixtures/entity_definition';
describe('generateProcessors(definition)', () => {
it('should genearte a valid pipeline', () => {
- const processors = generateProcessors(entityDefinition);
+ const spaceId = 'test';
+ const processors = generateProcessors(entityDefinition, spaceId);
expect(processors).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.ts b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.ts
index 33f27cc5daf71..70c3b34368b3a 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/lib/entities/ingest_pipeline/generate_processors.ts
@@ -43,7 +43,7 @@ function createMetadataPainlessScript(definition: EntityDefinition) {
}, '');
}
-export function generateProcessors(definition: EntityDefinition) {
+export function generateProcessors(definition: EntityDefinition, spaceId: string) {
return [
{
set: {
@@ -51,6 +51,12 @@ export function generateProcessors(definition: EntityDefinition) {
value: '{{{_ingest.timestamp}}}',
},
},
+ {
+ set: {
+ field: 'entity.spaceId',
+ value: spaceId,
+ },
+ },
{
set: {
field: 'entity.definitionId',
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts b/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts
index 4c947926cf571..ed60497a1cf65 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts
@@ -70,7 +70,12 @@ export class AssetManagerServerPlugin
});
const router = core.http.createRouter();
- setupRoutes({ router, assetClient, logger: this.logger });
+ setupRoutes({
+ router,
+ assetClient,
+ logger: this.logger,
+ spaces: plugins.spaces,
+ });
return {
assetClient,
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/create.ts b/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/create.ts
index d403c39ae0ed1..69ea6394ecb94 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/create.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/create.ts
@@ -24,6 +24,7 @@ import { ENTITY_API_PREFIX } from '../../../common/constants_entities';
export function createEntityDefinitionRoute({
router,
logger,
+ spaces,
}: SetupRouteOptions) {
router.post(
{
@@ -42,12 +43,15 @@ export function createEntityDefinitionRoute({
let definitionCreated = false;
let ingestPipelineCreated = false;
let transformCreated = false;
- const soClient = (await context.core).savedObjects.client;
- const esClient = (await context.core).elasticsearch.client.asCurrentUser;
+ const core = await context.core;
+ const soClient = core.savedObjects.client;
+ const esClient = core.elasticsearch.client.asCurrentUser;
+ const spaceId = spaces?.spacesService.getSpaceId(req) ?? 'default';
+
try {
const definition = await saveEntityDefinition(soClient, req.body);
definitionCreated = true;
- await createAndInstallIngestPipeline(esClient, definition, logger);
+ await createAndInstallIngestPipeline(esClient, definition, logger, spaceId);
ingestPipelineCreated = true;
await createAndInstallTransform(esClient, definition, logger);
transformCreated = true;
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/reset.ts b/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/reset.ts
index 3109ffc44520f..ffe53fee63577 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/reset.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/routes/entities/reset.ts
@@ -23,6 +23,7 @@ import { ENTITY_API_PREFIX } from '../../../common/constants_entities';
export function resetEntityDefinitionRoute({
router,
logger,
+ spaces,
}: SetupRouteOptions) {
router.post<{ id: string }, unknown, unknown>(
{
@@ -37,6 +38,7 @@ export function resetEntityDefinitionRoute({
try {
const soClient = (await context.core).savedObjects.client;
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
+ const spaceId = spaces?.spacesService.getSpaceId(req) ?? 'default';
const definition = await readEntityDefinition(soClient, req.params.id, logger);
@@ -46,7 +48,7 @@ export function resetEntityDefinitionRoute({
await deleteIndex(esClient, definition, logger);
// Recreate everything
- await createAndInstallIngestPipeline(esClient, definition, logger);
+ await createAndInstallIngestPipeline(esClient, definition, logger, spaceId);
await createAndInstallTransform(esClient, definition, logger);
await startTransform(esClient, definition, logger);
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/routes/index.ts b/x-pack/plugins/observability_solution/asset_manager/server/routes/index.ts
index d0b6c9f7ff0f1..759255b0eb5b1 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/routes/index.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/routes/index.ts
@@ -18,19 +18,15 @@ import { createEntityDefinitionRoute } from './entities/create';
import { deleteEntityDefinitionRoute } from './entities/delete';
import { resetEntityDefinitionRoute } from './entities/reset';
-export function setupRoutes({
- router,
- assetClient,
- logger,
-}: SetupRouteOptions) {
- pingRoute({ router, assetClient, logger });
- sampleAssetsRoutes({ router, assetClient, logger });
- assetsRoutes({ router, assetClient, logger });
- hostsRoutes({ router, assetClient, logger });
- servicesRoutes({ router, assetClient, logger });
- containersRoutes({ router, assetClient, logger });
- podsRoutes({ router, assetClient, logger });
- createEntityDefinitionRoute({ router, assetClient, logger });
- deleteEntityDefinitionRoute({ router, assetClient, logger });
- resetEntityDefinitionRoute({ router, assetClient, logger });
+export function setupRoutes(dependencies: SetupRouteOptions) {
+ pingRoute(dependencies);
+ sampleAssetsRoutes(dependencies);
+ assetsRoutes(dependencies);
+ hostsRoutes(dependencies);
+ servicesRoutes(dependencies);
+ containersRoutes(dependencies);
+ podsRoutes(dependencies);
+ createEntityDefinitionRoute(dependencies);
+ deleteEntityDefinitionRoute(dependencies);
+ resetEntityDefinitionRoute(dependencies);
}
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/routes/types.ts b/x-pack/plugins/observability_solution/asset_manager/server/routes/types.ts
index 561819d18cdae..b184cba6f9866 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/routes/types.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/routes/types.ts
@@ -7,10 +7,12 @@
import { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server';
import { Logger } from '@kbn/core/server';
+import { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import { AssetClient } from '../lib/asset_client';
export interface SetupRouteOptions {
router: IRouter;
assetClient: AssetClient;
logger: Logger;
+ spaces?: SpacesPluginSetup;
}
diff --git a/x-pack/plugins/observability_solution/asset_manager/server/types.ts b/x-pack/plugins/observability_solution/asset_manager/server/types.ts
index a3e2b2346e383..5a5764ba81111 100644
--- a/x-pack/plugins/observability_solution/asset_manager/server/types.ts
+++ b/x-pack/plugins/observability_solution/asset_manager/server/types.ts
@@ -11,6 +11,7 @@ import {
ApmDataAccessPluginStart,
} from '@kbn/apm-data-access-plugin/server';
import { MetricsDataPluginSetup } from '@kbn/metrics-data-access-plugin/server';
+import { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
export interface ElasticsearchAccessorOptions {
elasticsearchClient: ElasticsearchClient;
@@ -19,6 +20,7 @@ export interface ElasticsearchAccessorOptions {
export interface AssetManagerPluginSetupDependencies {
apmDataAccess: ApmDataAccessPluginSetup;
metricsDataAccess: MetricsDataPluginSetup;
+ spaces?: SpacesPluginSetup;
}
export interface AssetManagerPluginStartDependencies {
apmDataAccess: ApmDataAccessPluginStart;
diff --git a/x-pack/plugins/observability_solution/asset_manager/tsconfig.json b/x-pack/plugins/observability_solution/asset_manager/tsconfig.json
index dbbc36252bfb1..82d7632a48b3a 100644
--- a/x-pack/plugins/observability_solution/asset_manager/tsconfig.json
+++ b/x-pack/plugins/observability_solution/asset_manager/tsconfig.json
@@ -29,6 +29,7 @@
"@kbn/core-saved-objects-api-server-mocks",
"@kbn/entities-schema",
"@kbn/es-query",
- "@kbn/zod-helpers"
+ "@kbn/zod-helpers",
+ "@kbn/spaces-plugin"
]
}
From 57f74773a02d771b760170f05def6c063d2abe02 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Fri, 31 May 2024 12:01:11 -0400
Subject: [PATCH 19/46] [Fleet] Disable ready only advanced settings fields
(#184587)
---
.../fleet/components/form_settings/index.tsx | 67 +++++++++++--------
.../form_settings/settings_field_group.tsx | 11 ++-
.../form_settings/settings_field_wrapper.tsx | 5 +-
.../components/agent_policy_form.tsx | 18 +++--
4 files changed, 62 insertions(+), 39 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx
index 292e308ad578f..c91b98e82312b 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx
@@ -16,14 +16,14 @@ import { getInnerType, SettingsFieldWrapper } from './settings_field_wrapper';
export const settingComponentRegistry = new Map<
string,
- (settingsconfig: SettingsConfig) => React.ReactElement
+ (settingsconfig: SettingsConfig & { disabled?: boolean }) => React.ReactElement
>();
-settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodObject, (settingsConfig) => (
-
+settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodObject, ({ disabled, ...settingsConfig }) => (
+
));
-settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodNumber, (settingsConfig) => {
+settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodNumber, ({ disabled, ...settingsConfig }) => {
return (
(
{
+settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodString, ({ disabled, ...settingsConfig }) => {
return (
(
{
- return (
- (
- ({
- text: key,
- value: value as string,
- })
- )}
- />
- )}
- />
- );
-});
+settingComponentRegistry.set(
+ ZodFirstPartyTypeKind.ZodNativeEnum,
+ ({ disabled, ...settingsConfig }) => {
+ return (
+ (
+ ({
+ text: key,
+ value: value as string,
+ })
+ )}
+ />
+ )}
+ />
+ );
+ }
+);
export function ConfiguredSettings({
configuredSettings,
+ disabled,
}: {
configuredSettings: SettingsConfig[];
+ disabled?: boolean;
}) {
return (
<>
@@ -100,7 +109,9 @@ export function ConfiguredSettings({
throw new Error(`Unknown setting type: ${configuredSetting.schema._type}}`);
}
- return ;
+ return (
+
+ );
})}
>
);
diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_group.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_group.tsx
index 57d873d7a0560..091ca224c9fae 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_group.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_group.tsx
@@ -26,9 +26,10 @@ import {
validateSchema,
} from './settings_field_wrapper';
-export const SettingsFieldGroup: React.FC<{ settingsConfig: SettingsConfig }> = ({
- settingsConfig,
-}) => {
+export const SettingsFieldGroup: React.FC<{
+ settingsConfig: SettingsConfig;
+ disabled?: boolean;
+}> = ({ settingsConfig, disabled }) => {
const [errors, setErrors] = useState<{ [key: string]: string }>({});
const agentPolicyFormContext = useAgentPolicyFormContext();
const shape = settingsConfig.schema._def.innerType._def.shape();
@@ -90,6 +91,7 @@ export const SettingsFieldGroup: React.FC<{ settingsConfig: SettingsConfig }> =
return (
=
return (
=
return (
{
updateFieldValue(e.target.checked);
@@ -127,6 +131,7 @@ export const SettingsFieldGroup: React.FC<{ settingsConfig: SettingsConfig }> =
= ({ settingsConfig, typeName, renderItem }) => {
+ disabled?: boolean;
+}> = ({ settingsConfig, typeName, renderItem, disabled }) => {
const [error, setError] = useState('');
const agentPolicyFormContext = useAgentPolicyFormContext();
@@ -88,7 +89,7 @@ export const SettingsFieldWrapper: React.FC<{
>
}
>
-
+
{renderItem({ fieldValue, handleChange, isInvalid: !!error, fieldKey, coercedSchema })}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
index c1f99b786f248..2e98cd8a8172c 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
@@ -69,7 +69,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({
updateAdvancedSettingsHasErrors,
}) => {
const authz = useAuthz();
- const hasFleetAllAgentPoliciesPrivileges = !authz.fleet.allAgentPolicies;
+ const isDisabled = !authz.fleet.allAgentPolicies;
const { advancedPolicySettings } = ExperimentalFeaturesService.get();
@@ -104,7 +104,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({
agentPolicy={agentPolicy}
updateAgentPolicy={updateAgentPolicy}
validation={validation}
- disabled={hasFleetAllAgentPoliciesPrivileges}
+ disabled={isDisabled}
/>
) : (
generalSettingsWrapper([
@@ -112,7 +112,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({
agentPolicy={agentPolicy}
updateAgentPolicy={updateAgentPolicy}
validation={validation}
- disabled={hasFleetAllAgentPoliciesPrivileges}
+ disabled={isDisabled}
/>,
])
)}
@@ -156,7 +156,10 @@ export const AgentPolicyForm: React.FunctionComponent = ({
-
+
>
) : null}
@@ -167,7 +170,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({
agentPolicy={agentPolicy}
updateAgentPolicy={updateAgentPolicy}
validation={validation}
- disabled={hasFleetAllAgentPoliciesPrivileges}
+ disabled={isDisabled}
/>
{advancedPolicySettings ? (
<>
@@ -182,7 +185,10 @@ export const AgentPolicyForm: React.FunctionComponent = ({
-
+
>
) : null}
From 265e8799b83d50a15f00dbab769b989135b2cdea Mon Sep 17 00:00:00 2001
From: Alex Szabo
Date: Fri, 31 May 2024 18:16:08 +0200
Subject: [PATCH 20/46] [CI] Prevent deployment of missing/failed serverless
docker images (#184594)
## Summary
Removes `soft_fail` to prevent the run of the deploy script if something
about the image build fails.
Closes: https://github.com/elastic/kibana/issues/184591
---
.buildkite/pipelines/pull_request/deploy_project.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.buildkite/pipelines/pull_request/deploy_project.yml b/.buildkite/pipelines/pull_request/deploy_project.yml
index 4891e287723fd..0698d9e1899e6 100644
--- a/.buildkite/pipelines/pull_request/deploy_project.yml
+++ b/.buildkite/pipelines/pull_request/deploy_project.yml
@@ -9,7 +9,6 @@ steps:
machineType: n2-standard-16
preemptible: true
timeout_in_minutes: 60
- soft_fail: true
retry:
automatic:
- exit_status: '-1'
@@ -29,4 +28,3 @@ steps:
- linting_with_types
- checks
- check_types
- soft_fail: true
From 0f8befbe067f3f51d9ccf280c795b2efa8ec9efb Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Fri, 31 May 2024 17:43:13 +0100
Subject: [PATCH 21/46] skip flaky suite (#179353)
---
x-pack/test/accessibility/apps/group3/security_solution.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/accessibility/apps/group3/security_solution.ts b/x-pack/test/accessibility/apps/group3/security_solution.ts
index af62d92b345e1..2bf6228473d2f 100644
--- a/x-pack/test/accessibility/apps/group3/security_solution.ts
+++ b/x-pack/test/accessibility/apps/group3/security_solution.ts
@@ -31,7 +31,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
describe('Custom Query Rule', () => {
- describe('Define Step', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/179353
+ describe.skip('Define Step', () => {
it('default view meets a11y requirements', async () => {
await toasts.dismissAll();
await testSubjects.click('customRuleType');
From a78522d5c1ac748d42bbd68b93e65b5d722cb2ad Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Fri, 31 May 2024 17:44:21 +0100
Subject: [PATCH 22/46] Revert "skip flaky suite (#179353)"
This reverts commit 0f8befbe067f3f51d9ccf280c795b2efa8ec9efb.
---
x-pack/test/accessibility/apps/group3/security_solution.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/x-pack/test/accessibility/apps/group3/security_solution.ts b/x-pack/test/accessibility/apps/group3/security_solution.ts
index 2bf6228473d2f..af62d92b345e1 100644
--- a/x-pack/test/accessibility/apps/group3/security_solution.ts
+++ b/x-pack/test/accessibility/apps/group3/security_solution.ts
@@ -31,8 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
describe('Custom Query Rule', () => {
- // FLAKY: https://github.com/elastic/kibana/issues/179353
- describe.skip('Define Step', () => {
+ describe('Define Step', () => {
it('default view meets a11y requirements', async () => {
await toasts.dismissAll();
await testSubjects.click('customRuleType');
From 132bc97a850cc5d5b9b395deb7d718d2b4d829dd Mon Sep 17 00:00:00 2001
From: Yara Tercero
Date: Fri, 31 May 2024 10:25:45 -0700
Subject: [PATCH 23/46] [Detection Engine] Update internal endpoints to
indicate them as such (#184326)
While the Rules Management team was researching current status of OpenAPI migration in Security Solution, they found two of our endpoints that are /internal/... but marked as access:public. Updating the routes to be as intended - access: internal.
---
.../detection_engine/users/suggest_user_profiles_route.gen.ts | 2 +-
.../users/suggest_user_profiles_route.schema.yaml | 4 ++--
.../public/common/components/user_profiles/api.test.ts | 2 +-
.../public/common/components/user_profiles/api.ts | 2 +-
.../detections/containers/detection_engine/alerts/api.ts | 2 +-
.../routes/index/read_alerts_index_exists_route.ts | 4 ++--
.../routes/users/suggest_user_profiles_route.ts | 4 ++--
.../api_integration/services/security_solution_api.gen.ts | 2 +-
8 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.gen.ts
index 28e95e256d598..40b24aeb70e48 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.gen.ts
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.gen.ts
@@ -13,7 +13,7 @@ import { z } from 'zod';
*
* info:
* title: Suggest user profiles API endpoint
- * version: 2023-10-31
+ * version: 1
*/
export type SuggestUserProfilesRequestQuery = z.infer;
diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml
index c9e390da9fa17..a4778969d0312 100644
--- a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml
+++ b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: Suggest user profiles API endpoint
- version: '2023-10-31'
+ version: '1'
paths:
/internal/detection_engine/users/_find:
summary: Suggests user profiles based on provided search term
@@ -13,7 +13,7 @@ paths:
- name: searchTerm
in: query
required: false
- description: "Query string used to match name-related fields in user profiles. The following fields are treated as name-related: username, full_name and email"
+ description: 'Query string used to match name-related fields in user profiles. The following fields are treated as name-related: username, full_name and email'
schema:
type: string
responses:
diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/api.test.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.test.ts
index fbd2ee48c9eb6..f86f6ffcdb7b1 100644
--- a/x-pack/plugins/security_solution/public/common/components/user_profiles/api.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.test.ts
@@ -32,7 +32,7 @@ describe('Detections Alerts API', () => {
DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL,
expect.objectContaining({
method: 'GET',
- version: '2023-10-31',
+ version: '1',
query: { searchTerm: 'name1' },
})
);
diff --git a/x-pack/plugins/security_solution/public/common/components/user_profiles/api.ts b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.ts
index 22340d25e0a57..fb2f430232c53 100644
--- a/x-pack/plugins/security_solution/public/common/components/user_profiles/api.ts
+++ b/x-pack/plugins/security_solution/public/common/components/user_profiles/api.ts
@@ -21,7 +21,7 @@ export const suggestUsers = async ({
DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL,
{
method: 'GET',
- version: '2023-10-31',
+ version: '1',
query: { searchTerm },
}
);
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts
index e54e4f8e8ef39..815628f8a3d4a 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts
@@ -143,7 +143,7 @@ export const getSignalIndex = async ({ signal }: BasicSignals): Promise =>
KibanaServices.get().http.fetch(DETECTION_ENGINE_ALERTS_INDEX_URL, {
- version: '2023-10-31',
+ version: '1',
method: 'GET',
signal,
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts
index 56f37df726823..9f75689cf7811 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_alerts_index_exists_route.ts
@@ -17,14 +17,14 @@ export const readAlertsIndexExistsRoute = (router: SecuritySolutionPluginRouter)
router.versioned
.get({
path: DETECTION_ENGINE_ALERTS_INDEX_URL,
- access: 'public',
+ access: 'internal',
options: {
tags: ['access:securitySolution'],
},
})
.addVersion(
{
- version: '2023-10-31',
+ version: '1',
validate: false,
},
async (context, _, response): Promise> => {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts
index fcb42d2ead7e4..76787a82b7799 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts
@@ -22,14 +22,14 @@ export const suggestUserProfilesRoute = (
router.versioned
.get({
path: DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL,
- access: 'public',
+ access: 'internal',
options: {
tags: ['access:securitySolution'],
},
})
.addVersion(
{
- version: '2023-10-31',
+ version: '1',
validate: {
request: {
query: buildRouteValidationWithZod(SuggestUserProfilesRequestQuery),
diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts
index a36e5846b9261..26674427ae8e6 100644
--- a/x-pack/test/api_integration/services/security_solution_api.gen.ts
+++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts
@@ -277,7 +277,7 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext)
return supertest
.post('/internal/detection_engine/users/_find')
.set('kbn-xsrf', 'true')
- .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.query(props.query);
},
From c0d4fc70bdfe4f2195dc644e541ae0de0492ef9b Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Fri, 31 May 2024 10:43:16 -0700
Subject: [PATCH 24/46] [EuiProvider] Use KibanaRenderContextProvider in
Console A11y Overlay (#184488)
## Summary
Part of https://github.com/elastic/kibana-team/issues/805
Addresses functional test failure found in
https://github.com/elastic/kibana/pull/180819
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---
.../editor/legacy/console_editor/editor.tsx | 3 +-
.../monaco/monaco_editor_actions_provider.ts | 2 +-
.../containers/embeddable/console_wrapper.tsx | 18 +++--
.../contexts/services_context.mock.ts | 2 +-
.../application/contexts/services_context.tsx | 3 +-
.../use_send_current_request.ts | 2 +-
.../console/public/application/index.tsx | 7 +-
src/plugins/console/public/plugin.ts | 2 +-
.../ace/use_ui_ace_keyboard_mode.tsx | 19 +++--
src/plugins/es_ui_shared/tsconfig.json | 1 +
.../editor/editor.test.tsx | 4 +
.../profile_query_editor/editor/editor.tsx | 73 ++++++++++---------
.../profile_query_editor.tsx | 3 +-
.../application/contexts/app_context.tsx | 8 +-
.../public/application/index.tsx | 4 +-
15 files changed, 88 insertions(+), 63 deletions(-)
diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
index c53e1ff5dc2a5..abdd7c5a5ab96 100644
--- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
+++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx
@@ -69,6 +69,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
storage,
},
docLinkVersion,
+ ...startServices
} = useServicesContext();
const { settings } = useEditorReadContext();
@@ -80,7 +81,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
const editorInstanceRef = useRef(null);
const [textArea, setTextArea] = useState(null);
- useUIAceKeyboardMode(textArea, settings.isAccessibilityOverlayEnabled);
+ useUIAceKeyboardMode(textArea, startServices, settings.isAccessibilityOverlayEnabled);
const openDocumentation = useCallback(async () => {
const documentation = await getDocumentation(editorInstanceRef.current!, docLinkVersion);
diff --git a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts
index dbb6dc1845e41..8001966907272 100644
--- a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts
+++ b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts
@@ -184,7 +184,7 @@ export class MonacoEditorActionsProvider {
public async sendRequests(dispatch: Dispatch, context: ContextValue): Promise {
const {
services: { notifications, trackUiMetric, http, settings, history, autocompleteInfo },
- startServices,
+ ...startServices
} = context;
const { toasts } = notifications;
try {
diff --git a/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx b/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx
index 7921e128721b6..3a60a3706fa5b 100644
--- a/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx
+++ b/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx
@@ -34,7 +34,11 @@ import {
getStorage,
} from '../../../services';
import { createUsageTracker } from '../../../services/tracker';
-import { MetricsTracker, EmbeddableConsoleDependencies } from '../../../types';
+import {
+ MetricsTracker,
+ EmbeddableConsoleDependencies,
+ ConsoleStartServices,
+} from '../../../types';
import { createApi, createEsHostService } from '../../lib';
import { EsHostService } from '../../lib/es_host_service';
@@ -47,7 +51,7 @@ import {
import { Main } from '../main';
import { EditorContentSpinner } from '../../components';
-interface ConsoleDependencies {
+interface ConsoleDependencies extends ConsoleStartServices {
autocompleteInfo: AutocompleteInfo;
docLinks: DocLinksStart['links'];
docLinkVersion: string;
@@ -70,7 +74,7 @@ const loadDependencies = async (
docLinks: { DOC_LINK_VERSION, links },
http,
notifications,
- theme: { theme$ },
+ ...startServices
} = core;
const trackUiMetric = createUsageTracker(usageCollection);
trackUiMetric.load('opened_embedded_app');
@@ -86,6 +90,7 @@ const loadDependencies = async (
autocompleteInfo.mapping.setup(http, settings);
return {
+ ...startServices,
autocompleteInfo,
docLinks: links,
docLinkVersion: DOC_LINK_VERSION,
@@ -96,7 +101,7 @@ const loadDependencies = async (
objectStorageClient,
settings,
storage,
- theme$,
+ theme$: startServices.theme.theme$,
trackUiMetric,
};
};
@@ -113,8 +118,6 @@ interface ConsoleWrapperProps
export const ConsoleWrapper = (props: ConsoleWrapperProps) => {
const [dependencies, setDependencies] = useState(null);
const { core, usageCollection, onKeyDown, isMonacoEnabled, isOpen } = props;
- const { analytics, i18n, theme } = core;
- const startServices = { analytics, i18n, theme };
useEffect(() => {
if (dependencies === null && isOpen) {
@@ -144,11 +147,13 @@ export const ConsoleWrapper = (props: ConsoleWrapperProps) => {
settings,
storage,
trackUiMetric,
+ ...startServices
} = dependencies;
return (
{
config: {
isMonacoEnabled,
},
- startServices,
}}
>
diff --git a/src/plugins/console/public/application/contexts/services_context.mock.ts b/src/plugins/console/public/application/contexts/services_context.mock.ts
index fc22676b9f72d..e17b4e529a198 100644
--- a/src/plugins/console/public/application/contexts/services_context.mock.ts
+++ b/src/plugins/console/public/application/contexts/services_context.mock.ts
@@ -32,6 +32,7 @@ export const serviceContextMock = {
const esHostService = createEsHostService({ api });
(storage.keys as jest.Mock).mockImplementation(() => []);
return {
+ ...coreStart,
services: {
trackUiMetric: { count: () => {}, load: () => {} },
storage,
@@ -48,7 +49,6 @@ export const serviceContextMock = {
config: {
isMonacoEnabled: false,
},
- startServices: coreStart,
};
},
};
diff --git a/src/plugins/console/public/application/contexts/services_context.tsx b/src/plugins/console/public/application/contexts/services_context.tsx
index 89e7704023a09..d8885b8ce0788 100644
--- a/src/plugins/console/public/application/contexts/services_context.tsx
+++ b/src/plugins/console/public/application/contexts/services_context.tsx
@@ -26,14 +26,13 @@ interface ContextServices {
autocompleteInfo: AutocompleteInfo;
}
-export interface ContextValue {
+export interface ContextValue extends ConsoleStartServices {
services: ContextServices;
docLinkVersion: string;
docLinks: DocLinksStart['links'];
config: {
isMonacoEnabled: boolean;
};
- startServices: ConsoleStartServices;
}
interface ContextProps {
diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts
index 723806d5c735b..ee7f22ecbd8a4 100644
--- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts
+++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts
@@ -24,7 +24,7 @@ import { SenseEditor } from '../../models';
export const useSendCurrentRequest = () => {
const {
services: { history, settings, notifications, trackUiMetric, http, autocompleteInfo, storage },
- startServices,
+ ...startServices
} = useServicesContext();
const dispatch = useRequestActionContext();
diff --git a/src/plugins/console/public/application/index.tsx b/src/plugins/console/public/application/index.tsx
index a2d8cfb0cab18..0b140d105aa53 100644
--- a/src/plugins/console/public/application/index.tsx
+++ b/src/plugins/console/public/application/index.tsx
@@ -27,7 +27,7 @@ import { ServicesContextProvider, EditorContextProvider, RequestContextProvider
import { createApi, createEsHostService } from './lib';
import { ConsoleStartServices } from '../types';
-export interface BootDependencies {
+export interface BootDependencies extends ConsoleStartServices {
http: HttpSetup;
docLinkVersion: string;
notifications: NotificationsSetup;
@@ -36,7 +36,6 @@ export interface BootDependencies {
docLinks: DocLinksStart['links'];
autocompleteInfo: AutocompleteInfo;
isMonacoEnabled: boolean;
- startServices: ConsoleStartServices;
}
export async function renderApp({
@@ -48,7 +47,7 @@ export async function renderApp({
docLinks,
autocompleteInfo,
isMonacoEnabled,
- startServices,
+ ...startServices
}: BootDependencies) {
const trackUiMetric = createUsageTracker(usageCollection);
trackUiMetric.load('opened_app');
@@ -71,6 +70,7 @@ export async function renderApp({
diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts
index f1cae7206ea4d..05b26da2c110a 100644
--- a/src/plugins/console/public/plugin.ts
+++ b/src/plugins/console/public/plugin.ts
@@ -90,6 +90,7 @@ export class ConsoleUIPlugin
const { renderApp } = await import('./application');
return renderApp({
+ ...startServices,
http,
docLinkVersion: DOC_LINK_VERSION,
docLinks: links,
@@ -98,7 +99,6 @@ export class ConsoleUIPlugin
element,
autocompleteInfo: this.autocompleteInfo,
isMonacoEnabled,
- startServices,
});
},
});
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx
index 528f3a68ca134..64a34e8756ad0 100644
--- a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx
@@ -9,23 +9,32 @@
import React, { useEffect, useRef } from 'react';
import * as ReactDOM from 'react-dom';
import { keys, EuiText } from '@elastic/eui';
+import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import './_ui_ace_keyboard_mode.scss';
+import type { AnalyticsServiceStart, I18nStart, ThemeServiceStart } from '@kbn/core/public';
-const OverlayText = () => (
+interface StartServices {
+ analytics: Pick;
+ i18n: I18nStart;
+ theme: Pick;
+}
+
+const OverlayText = (startServices: StartServices) => (
// The point of this element is for accessibility purposes, so ignore eslint error
// in this case
//
- <>
+
Press Enter to start editing.
When you’re done, press Escape to stop editing.
- >
+
);
export function useUIAceKeyboardMode(
aceTextAreaElement: HTMLTextAreaElement | null,
+ startServices: StartServices,
isAccessibilityOverlayEnabled: boolean = true
) {
const overlayMountNode = useRef(null);
@@ -75,7 +84,7 @@ export function useUIAceKeyboardMode(
overlayMountNode.current.addEventListener('focus', enableOverlay);
overlayMountNode.current.addEventListener('keydown', onDismissOverlay);
- ReactDOM.render(, overlayMountNode.current);
+ ReactDOM.render(, overlayMountNode.current);
aceTextAreaElement.parentElement!.insertBefore(overlayMountNode.current, aceTextAreaElement);
aceTextAreaElement.setAttribute('tabindex', '-1');
@@ -99,5 +108,5 @@ export function useUIAceKeyboardMode(
}
}
};
- }, [aceTextAreaElement, isAccessibilityOverlayEnabled]);
+ }, [aceTextAreaElement, startServices, isAccessibilityOverlayEnabled]);
}
diff --git a/src/plugins/es_ui_shared/tsconfig.json b/src/plugins/es_ui_shared/tsconfig.json
index 7164d82402337..508146c5f8f40 100644
--- a/src/plugins/es_ui_shared/tsconfig.json
+++ b/src/plugins/es_ui_shared/tsconfig.json
@@ -24,6 +24,7 @@
"@kbn/storybook",
"@kbn/shared-ux-link-redirect-app",
"@kbn/code-editor",
+ "@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx
index f92e2b2a5167c..34e0867df8ec6 100644
--- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx
+++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx
@@ -8,12 +8,16 @@
import 'brace';
import 'brace/mode/json';
+import { coreMock } from '@kbn/core/public/mocks';
import { registerTestBed } from '@kbn/test-jest-helpers';
import { Editor, Props } from './editor';
+const coreStart = coreMock.createStart();
+
describe('Editor Component', () => {
it('renders', async () => {
const props: Props = {
+ ...coreStart,
initialValue: '',
licenseEnabled: true,
onEditorReady: (e: any) => {},
diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx
index ab3bfe0559a04..068673d4ce4c1 100644
--- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx
+++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.tsx
@@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import { EuiScreenReaderOnly } from '@elastic/eui';
import { Editor as AceEditor } from 'brace';
+import { SearchProfilerStartServices } from '../../../../types';
import { ace } from '../../../../shared_imports';
import { initializeEditor } from './init_editor';
@@ -19,7 +20,7 @@ type EditorShim = ReturnType;
export type EditorInstance = EditorShim;
-export interface Props {
+export interface Props extends SearchProfilerStartServices {
licenseEnabled: boolean;
initialValue: string;
onEditorReady: (editor: EditorShim) => void;
@@ -38,43 +39,45 @@ const createEditorShim = (aceEditor: AceEditor) => {
const EDITOR_INPUT_ID = 'SearchProfilerTextArea';
-export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Props) => {
- const containerRef = useRef(null as any);
- const editorInstanceRef = useRef(null as any);
+export const Editor = memo(
+ ({ licenseEnabled, initialValue, onEditorReady, ...startServices }: Props) => {
+ const containerRef = useRef(null as any);
+ const editorInstanceRef = useRef(null as any);
- const [textArea, setTextArea] = useState(null);
+ const [textArea, setTextArea] = useState(null);
- useUIAceKeyboardMode(textArea);
+ useUIAceKeyboardMode(textArea, startServices);
- useEffect(() => {
- const divEl = containerRef.current;
- editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled });
- editorInstanceRef.current.setValue(initialValue, 1);
- const textarea = divEl.querySelector('textarea');
- if (textarea) {
- textarea.setAttribute('id', EDITOR_INPUT_ID);
- }
- setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null);
+ useEffect(() => {
+ const divEl = containerRef.current;
+ editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled });
+ editorInstanceRef.current.setValue(initialValue, 1);
+ const textarea = divEl.querySelector('textarea');
+ if (textarea) {
+ textarea.setAttribute('id', EDITOR_INPUT_ID);
+ }
+ setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null);
- onEditorReady(createEditorShim(editorInstanceRef.current));
+ onEditorReady(createEditorShim(editorInstanceRef.current));
- return () => {
- if (editorInstanceRef.current) {
- editorInstanceRef.current.destroy();
- }
- };
- }, [initialValue, onEditorReady, licenseEnabled]);
+ return () => {
+ if (editorInstanceRef.current) {
+ editorInstanceRef.current.destroy();
+ }
+ };
+ }, [initialValue, onEditorReady, licenseEnabled]);
- return (
- <>
-
-
-
-
- >
- );
-});
+ return (
+ <>
+
+
+
+
+ >
+ );
+ }
+);
diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx
index c7188fe449317..574f8c7e18b60 100644
--- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx
+++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx
@@ -44,7 +44,7 @@ export const ProfileQueryEditor = memo(() => {
const dispatch = useProfilerActionContext();
- const { getLicenseStatus, notifications, location } = useAppContext();
+ const { getLicenseStatus, notifications, location, ...startServices } = useAppContext();
const queryParams = new URLSearchParams(location.search);
const indexName = queryParams.get('index');
@@ -119,6 +119,7 @@ export const ProfileQueryEditor = memo(() => {
onEditorReady={onEditorReady}
licenseEnabled={licenseEnabled}
initialValue={searchProfilerQuery ? searchProfilerQuery : INITIAL_EDITOR_VALUE}
+ {...startServices}
/>
diff --git a/x-pack/plugins/searchprofiler/public/application/contexts/app_context.tsx b/x-pack/plugins/searchprofiler/public/application/contexts/app_context.tsx
index dab6c9573cb1f..f4fad5eca9a9f 100644
--- a/x-pack/plugins/searchprofiler/public/application/contexts/app_context.tsx
+++ b/x-pack/plugins/searchprofiler/public/application/contexts/app_context.tsx
@@ -10,15 +10,16 @@ import { RouteComponentProps } from 'react-router-dom';
import { HttpSetup, ToastsSetup } from '@kbn/core/public';
import { LicenseStatus } from '../../../common';
+import { SearchProfilerStartServices } from '../../types';
-export interface ContextArgs {
+export interface ContextArgs extends SearchProfilerStartServices {
http: HttpSetup;
notifications: ToastsSetup;
initialLicenseStatus: LicenseStatus;
location: RouteComponentProps['location'];
}
-export interface ContextValue {
+export interface ContextValue extends SearchProfilerStartServices {
http: HttpSetup;
notifications: ToastsSetup;
getLicenseStatus: () => LicenseStatus;
@@ -29,7 +30,7 @@ const AppContext = createContext(null as any);
export const AppContextProvider = ({
children,
- args: { http, notifications, initialLicenseStatus, location },
+ args: { http, notifications, initialLicenseStatus, location, ...startServices },
}: {
children: React.ReactNode;
args: ContextArgs;
@@ -39,6 +40,7 @@ export const AppContextProvider = ({
return (
{
render(
-
+
From 863c4f3d045af44eff46d7403ff570b363acbe29 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 31 May 2024 10:49:59 -0700
Subject: [PATCH 25/46] Update dependency geckodriver to ^4.4.1 (main)
(#184525)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[geckodriver](https://togithub.com/webdriverio-community/node-geckodriver)
| [`^4.4.0` ->
`^4.4.1`](https://renovatebot.com/diffs/npm/geckodriver/4.4.0/4.4.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/geckodriver/4.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/geckodriver/4.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/geckodriver/4.4.0/4.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/geckodriver/4.4.0/4.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
---
### Release Notes
webdriverio-community/node-geckodriver (geckodriver)
###
[`v4.4.1`](https://togithub.com/webdriverio-community/node-geckodriver/releases/tag/v4.4.1):
Release 4.4.1
[Compare
Source](https://togithub.com/webdriverio-community/node-geckodriver/compare/v4.4.0...v4.4.1)
- fix install multi zip entries
([#450](https://togithub.com/webdriverio-community/node-geckodriver/issues/450))
([`3244189`](https://togithub.com/webdriverio-community/node-geckodriver/commit/3244189))
- chore(deps-dev): bump
[@typescript-eslint/parser](https://togithub.com/typescript-eslint/parser)
from 7.9.0 to 7.10.0
([#448](https://togithub.com/webdriverio-community/node-geckodriver/issues/448))
([`af917c8`](https://togithub.com/webdriverio-community/node-geckodriver/commit/af917c8))
- chore(deps-dev): bump webdriverio from 8.36.1 to 8.38.0
([#449](https://togithub.com/webdriverio-community/node-geckodriver/issues/449))
([`7c7d776`](https://togithub.com/webdriverio-community/node-geckodriver/commit/7c7d776))
- chore(deps-dev): bump release-it from 17.2.1 to 17.3.0
([#447](https://togithub.com/webdriverio-community/node-geckodriver/issues/447))
([`c1fbcf8`](https://togithub.com/webdriverio-community/node-geckodriver/commit/c1fbcf8))
- chore(deps-dev): bump
[@typescript-eslint/eslint-plugin](https://togithub.com/typescript-eslint/eslint-plugin)
([#446](https://togithub.com/webdriverio-community/node-geckodriver/issues/446))
([`739af52`](https://togithub.com/webdriverio-community/node-geckodriver/commit/739af52))
- chore(deps-dev): bump tsx from 4.10.5 to 4.11.0
([#445](https://togithub.com/webdriverio-community/node-geckodriver/issues/445))
([`4e9a973`](https://togithub.com/webdriverio-community/node-geckodriver/commit/4e9a973))
- chore(deps): bump
[@wdio/logger](https://togithub.com/wdio/logger) from 8.28.0 to
8.38.0
([#444](https://togithub.com/webdriverio-community/node-geckodriver/issues/444))
([`01494f5`](https://togithub.com/webdriverio-community/node-geckodriver/commit/01494f5))
- ci: revert
([`f7e4685`](https://togithub.com/webdriverio-community/node-geckodriver/commit/f7e4685))
- ci: skip auth for expensing
([`dce3599`](https://togithub.com/webdriverio-community/node-geckodriver/commit/dce3599))
- ci: update action
([`c5c521c`](https://togithub.com/webdriverio-community/node-geckodriver/commit/c5c521c))
- feat: migrate to zip.js
([#443](https://togithub.com/webdriverio-community/node-geckodriver/issues/443))
([`dccc1d6`](https://togithub.com/webdriverio-community/node-geckodriver/commit/dccc1d6))
- chore(deps-dev): bump tsx from 4.10.1 to 4.10.5
([#438](https://togithub.com/webdriverio-community/node-geckodriver/issues/438))
([`805a880`](https://togithub.com/webdriverio-community/node-geckodriver/commit/805a880))
- chore(deps-dev): bump
[@types/node](https://togithub.com/types/node) from 20.12.11 to
20.12.12
([#437](https://togithub.com/webdriverio-community/node-geckodriver/issues/437))
([`0faf445`](https://togithub.com/webdriverio-community/node-geckodriver/commit/0faf445))
- chore(deps-dev): bump
[@typescript-eslint/eslint-plugin](https://togithub.com/typescript-eslint/eslint-plugin)
([#439](https://togithub.com/webdriverio-community/node-geckodriver/issues/439))
([`83ea7c9`](https://togithub.com/webdriverio-community/node-geckodriver/commit/83ea7c9))
- chore(deps-dev): bump npm-run-all2 from 6.1.2 to 6.2.0
([#436](https://togithub.com/webdriverio-community/node-geckodriver/issues/436))
([`9a18155`](https://togithub.com/webdriverio-community/node-geckodriver/commit/9a18155))
- chore(deps-dev): bump
[@typescript-eslint/parser](https://togithub.com/typescript-eslint/parser)
from 7.8.0 to 7.9.0
([#435](https://togithub.com/webdriverio-community/node-geckodriver/issues/435))
([`2179627`](https://togithub.com/webdriverio-community/node-geckodriver/commit/2179627))
- chore(deps-dev): bump
[@types/node](https://togithub.com/types/node) from 20.12.8 to
20.12.11
([#432](https://togithub.com/webdriverio-community/node-geckodriver/issues/432))
([`cd0fdd7`](https://togithub.com/webdriverio-community/node-geckodriver/commit/cd0fdd7))
- chore(deps-dev): bump eslint-plugin-unicorn from 52.0.0 to 53.0.0
([#434](https://togithub.com/webdriverio-community/node-geckodriver/issues/434))
([`0e77506`](https://togithub.com/webdriverio-community/node-geckodriver/commit/0e77506))
- chore(deps): bump unzipper from 0.11.5 to 0.11.6
([#433](https://togithub.com/webdriverio-community/node-geckodriver/issues/433))
([`e32d34c`](https://togithub.com/webdriverio-community/node-geckodriver/commit/e32d34c))
- chore(deps-dev): bump octokit from 3.2.1 to 4.0.2
([#431](https://togithub.com/webdriverio-community/node-geckodriver/issues/431))
([`8479404`](https://togithub.com/webdriverio-community/node-geckodriver/commit/8479404))
- chore(deps-dev): bump tsx from 4.9.2 to 4.10.1
([#430](https://togithub.com/webdriverio-community/node-geckodriver/issues/430))
([`9898d82`](https://togithub.com/webdriverio-community/node-geckodriver/commit/9898d82))
- chore(deps-dev): bump
[@vitest/coverage-v8](https://togithub.com/vitest/coverage-v8)
from 1.5.2 to 1.6.0
([#429](https://togithub.com/webdriverio-community/node-geckodriver/issues/429))
([`5e7b854`](https://togithub.com/webdriverio-community/node-geckodriver/commit/5e7b854))
- chore(deps-dev): bump
[@typescript-eslint/parser](https://togithub.com/typescript-eslint/parser)
from 7.7.1 to 7.8.0
([#427](https://togithub.com/webdriverio-community/node-geckodriver/issues/427))
([`e585dca`](https://togithub.com/webdriverio-community/node-geckodriver/commit/e585dca))
- chore(deps-dev): bump octokit from 3.2.0 to 3.2.1
([#428](https://togithub.com/webdriverio-community/node-geckodriver/issues/428))
([`3b119a0`](https://togithub.com/webdriverio-community/node-geckodriver/commit/3b119a0))
- chore(deps-dev): bump tsx from 4.7.3 to 4.9.2
([#426](https://togithub.com/webdriverio-community/node-geckodriver/issues/426))
([`73d4a9b`](https://togithub.com/webdriverio-community/node-geckodriver/commit/73d4a9b))
- chore(deps-dev): bump
[@types/node](https://togithub.com/types/node) from 20.12.7 to
20.12.8
([#425](https://togithub.com/webdriverio-community/node-geckodriver/issues/425))
([`540dac7`](https://togithub.com/webdriverio-community/node-geckodriver/commit/540dac7))
- chore(deps-dev): bump
[@typescript-eslint/eslint-plugin](https://togithub.com/typescript-eslint/eslint-plugin)
([#424](https://togithub.com/webdriverio-community/node-geckodriver/issues/424))
([`9a2ef8d`](https://togithub.com/webdriverio-community/node-geckodriver/commit/9a2ef8d))
- chore(deps): bump unzipper from 0.11.4 to 0.11.5
([#423](https://togithub.com/webdriverio-community/node-geckodriver/issues/423))
([`b2f32b4`](https://togithub.com/webdriverio-community/node-geckodriver/commit/b2f32b4))
---
### Configuration
📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/elastic/kibana).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tiago Costa
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 51 +++++++++++++++------------------------------------
2 files changed, 16 insertions(+), 37 deletions(-)
diff --git a/package.json b/package.json
index d003ac456330f..e25a0dee10b2a 100644
--- a/package.json
+++ b/package.json
@@ -1617,7 +1617,7 @@
"file-loader": "^4.2.0",
"find-cypress-specs": "^1.41.4",
"form-data": "^4.0.0",
- "geckodriver": "^4.4.0",
+ "geckodriver": "^4.4.1",
"gulp-brotli": "^3.0.0",
"gulp-postcss": "^9.0.1",
"gulp-terser": "^2.1.0",
diff --git a/yarn.lock b/yarn.lock
index 0fbf065004194..49bf626c16184 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11402,6 +11402,11 @@
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+"@zip.js/zip.js@^2.7.44":
+ version "2.7.45"
+ resolved "https://registry.yarnpkg.com/@zip.js/zip.js/-/zip.js-2.7.45.tgz#823fe2789401d8c1d836ce866578379ec1bd6f0b"
+ integrity sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow==
+
abab@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
@@ -12667,7 +12672,7 @@ bezier-easing@^2.1.0:
resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
integrity sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==
-big-integer@^1.6.17, big-integer@^1.6.7:
+big-integer@^1.6.7:
version "1.6.51"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
@@ -12737,11 +12742,6 @@ bluebird@^3.3.5, bluebird@^3.5.5, bluebird@^3.7.2:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-bluebird@~3.4.1:
- version "3.4.7"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
- integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==
-
blurhash@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.1.tgz#7f134ad0cf3cbb6bcceb81ea51b82e1423009dca"
@@ -17896,16 +17896,6 @@ fsevents@2.3.2, fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
-fstream@^1.0.12:
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
- integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
- dependencies:
- graceful-fs "^4.1.2"
- inherits "~2.0.0"
- mkdirp ">=0.5 0"
- rimraf "2"
-
fsu@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fsu/-/fsu-1.1.1.tgz#bd36d3579907c59d85b257a75b836aa9e0c31834"
@@ -17946,18 +17936,18 @@ gauge@^3.0.0:
strip-ansi "^6.0.1"
wide-align "^1.1.2"
-geckodriver@^4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-4.4.0.tgz#c2703841e59dd95ee1f1a29b9d9b3cfb38da7c0d"
- integrity sha512-Y/Np2VkAhBkJoFAIY3pKH3rICUcR5rH9VD6EHwh0CqUIh6Opzr/NFwfcQenYfbRT/659R15/35LpA1s6h9wPPg==
+geckodriver@^4.4.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-4.4.1.tgz#b39b26a17f9166038702743f5722b6d83e0483f6"
+ integrity sha512-nnAdIrwLkMcDu4BitWXF23pEMeZZ0Cj7HaWWFdSpeedBP9z6ft150JYiGO2mwzw6UiR823Znk1JeIf07RyzloA==
dependencies:
"@wdio/logger" "^8.28.0"
+ "@zip.js/zip.js" "^2.7.44"
decamelize "^6.0.0"
http-proxy-agent "^7.0.2"
https-proxy-agent "^7.0.4"
node-fetch "^3.3.2"
tar-fs "^3.0.6"
- unzipper "^0.11.4"
which "^4.0.0"
gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
@@ -18429,7 +18419,7 @@ graceful-fs@4.2.10:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.8, graceful-fs@^4.2.9:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.8, graceful-fs@^4.2.9:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -19300,7 +19290,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -22687,7 +22677,7 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
-"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
+mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@@ -27151,7 +27141,7 @@ rgbcolor@^1.0.1:
resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d"
integrity sha1-1lBezbMEplldom+ktDMHMGd1lF0=
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
+rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -30406,17 +30396,6 @@ untildify@^4.0.0:
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
-unzipper@^0.11.4:
- version "0.11.6"
- resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.11.6.tgz#05b9954320ab30c9398b1638db791c112f919075"
- integrity sha512-anERl79akvqLbAxfjIFe4hK0wsi0fH4uGLwNEl4QEnG+KKs3QQeApYgOS/f6vH2EdACUlZg35psmd/3xL2duFQ==
- dependencies:
- big-integer "^1.6.17"
- bluebird "~3.4.1"
- duplexer2 "~0.1.4"
- fstream "^1.0.12"
- graceful-fs "^4.2.2"
-
update-browserslist-db@^1.0.13:
version "1.0.13"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
From 9a6dacb6978d32ebf5fa5c0a4439b4dc5476e426 Mon Sep 17 00:00:00 2001
From: Yngrid Coello
Date: Fri, 31 May 2024 19:50:09 +0200
Subject: [PATCH 26/46] [Dataset quality] Added dataQuality locator (#184588)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes https://github.com/elastic/kibana/issues/183406
## 📝 Summary
This PR adds a basic locator to dataQuality plugin, where timeRange
filters can be shared
## 🎥 Demo
https://github.com/elastic/kibana/assets/1313018/47dabe6f-fe89-4075-8688-1e53332cdd9a
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/data_quality/common/index.ts | 3 +
.../construct_dataset_quality_locator_path.ts | 52 ++++++++++
.../locators/dataset_quality_locator.ts | 33 +++++++
.../data_quality/common/locators/index.ts | 15 +++
.../common/locators/locators.test.ts | 77 +++++++++++++++
.../data_quality/common/locators/types.ts | 39 ++++++++
x-pack/plugins/data_quality/jest.config.js | 15 +++
x-pack/plugins/data_quality/kibana.jsonc | 1 +
x-pack/plugins/data_quality/public/plugin.ts | 18 +++-
x-pack/plugins/data_quality/public/types.ts | 3 +
x-pack/plugins/data_quality/tsconfig.json | 3 +
.../common/translations.ts | 7 ++
.../components/dataset_quality_link.tsx | 98 +++++++++++++++++++
.../observability_logs_explorer/tsconfig.json | 1 +
14 files changed, 364 insertions(+), 1 deletion(-)
create mode 100644 x-pack/plugins/data_quality/common/locators/construct_dataset_quality_locator_path.ts
create mode 100644 x-pack/plugins/data_quality/common/locators/dataset_quality_locator.ts
create mode 100644 x-pack/plugins/data_quality/common/locators/index.ts
create mode 100644 x-pack/plugins/data_quality/common/locators/locators.test.ts
create mode 100644 x-pack/plugins/data_quality/common/locators/types.ts
create mode 100644 x-pack/plugins/data_quality/jest.config.js
create mode 100644 x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx
diff --git a/x-pack/plugins/data_quality/common/index.ts b/x-pack/plugins/data_quality/common/index.ts
index 25831a8bd3d3c..e92f9084edac7 100644
--- a/x-pack/plugins/data_quality/common/index.ts
+++ b/x-pack/plugins/data_quality/common/index.ts
@@ -13,3 +13,6 @@ export const PLUGIN_NAME = i18n.translate('xpack.dataQuality.name', {
});
export { DATA_QUALITY_URL_STATE_KEY, datasetQualityUrlSchemaV1 } from './url_schema';
+
+export { DATA_QUALITY_LOCATOR_ID } from './locators';
+export type { DataQualityLocatorParams } from './locators';
diff --git a/x-pack/plugins/data_quality/common/locators/construct_dataset_quality_locator_path.ts b/x-pack/plugins/data_quality/common/locators/construct_dataset_quality_locator_path.ts
new file mode 100644
index 0000000000000..45f58752bd2fc
--- /dev/null
+++ b/x-pack/plugins/data_quality/common/locators/construct_dataset_quality_locator_path.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common';
+import { ManagementAppLocatorParams } from '@kbn/management-plugin/common/locator';
+import { LocatorPublic } from '@kbn/share-plugin/common';
+import { datasetQualityUrlSchemaV1, DATA_QUALITY_URL_STATE_KEY } from '../url_schema';
+import { deepCompactObject } from '../utils/deep_compact_object';
+import { DataQualityLocatorParams } from './types';
+
+interface LocatorPathConstructionParams {
+ locatorParams: DataQualityLocatorParams;
+ useHash: boolean;
+ managementLocator: LocatorPublic;
+}
+
+export const constructDatasetQualityLocatorPath = async (params: LocatorPathConstructionParams) => {
+ const {
+ locatorParams: { filters },
+ useHash,
+ managementLocator,
+ } = params;
+
+ const pageState = datasetQualityUrlSchemaV1.urlSchemaRT.encode(
+ deepCompactObject({
+ v: 1,
+ filters,
+ })
+ );
+
+ const managementPath = await managementLocator.getLocation({
+ sectionId: 'data',
+ appId: 'data_quality',
+ });
+
+ const path = setStateToKbnUrl(
+ DATA_QUALITY_URL_STATE_KEY,
+ pageState,
+ { useHash, storeInHashQuery: false },
+ `${managementPath.app}${managementPath.path}`
+ );
+
+ return {
+ app: '',
+ path,
+ state: {},
+ };
+};
diff --git a/x-pack/plugins/data_quality/common/locators/dataset_quality_locator.ts b/x-pack/plugins/data_quality/common/locators/dataset_quality_locator.ts
new file mode 100644
index 0000000000000..70e4770090ef3
--- /dev/null
+++ b/x-pack/plugins/data_quality/common/locators/dataset_quality_locator.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 type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
+import {
+ DataQualityLocatorDependencies,
+ DataQualityLocatorParams,
+ DATA_QUALITY_LOCATOR_ID,
+} from './types';
+import { constructDatasetQualityLocatorPath } from './construct_dataset_quality_locator_path';
+
+export type DatasetQualityLocator = LocatorPublic;
+
+export class DatasetQualityLocatorDefinition
+ implements LocatorDefinition
+{
+ public readonly id = DATA_QUALITY_LOCATOR_ID;
+
+ constructor(protected readonly deps: DataQualityLocatorDependencies) {}
+
+ public readonly getLocation = async (params: DataQualityLocatorParams) => {
+ const { useHash, managementLocator } = this.deps;
+ return await constructDatasetQualityLocatorPath({
+ useHash,
+ managementLocator,
+ locatorParams: params,
+ });
+ };
+}
diff --git a/x-pack/plugins/data_quality/common/locators/index.ts b/x-pack/plugins/data_quality/common/locators/index.ts
new file mode 100644
index 0000000000000..97df9e70698d7
--- /dev/null
+++ b/x-pack/plugins/data_quality/common/locators/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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DatasetQualityLocator } from './dataset_quality_locator';
+
+export * from './dataset_quality_locator';
+export * from './types';
+
+export interface DataQualityLocators {
+ datasetQualityLocator: DatasetQualityLocator;
+}
diff --git a/x-pack/plugins/data_quality/common/locators/locators.test.ts b/x-pack/plugins/data_quality/common/locators/locators.test.ts
new file mode 100644
index 0000000000000..047ab98f75418
--- /dev/null
+++ b/x-pack/plugins/data_quality/common/locators/locators.test.ts
@@ -0,0 +1,77 @@
+/*
+ * 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 { DatasetQualityLocatorDefinition } from './dataset_quality_locator';
+import { DataQualityLocatorDependencies } from './types';
+
+const createMockLocator = (id: string, section: string) => ({
+ id,
+ navigate: jest.fn(),
+ getRedirectUrl: jest.fn(),
+ getLocation: jest.fn().mockReturnValue({ app: id, path: `/${section}`, state: {} }),
+ getUrl: jest.fn(),
+ navigateSync: jest.fn(),
+ useUrl: jest.fn(),
+ telemetry: jest.fn(),
+ inject: jest.fn(),
+ extract: jest.fn(),
+ migrations: jest.fn(),
+});
+
+const setup = async () => {
+ const dep: DataQualityLocatorDependencies = {
+ useHash: false,
+ managementLocator: createMockLocator('management', 'data/data_quality'),
+ };
+
+ const datasetQualityLocator = new DatasetQualityLocatorDefinition(dep);
+
+ return {
+ datasetQualityLocator,
+ };
+};
+
+describe('Data quality Locators', () => {
+ const timeRange = { to: 'now', from: 'now-30m' };
+
+ describe('Dataset Quality Locator', () => {
+ it('should create a link with correct path and no state', async () => {
+ const { datasetQualityLocator } = await setup();
+ const location = await datasetQualityLocator.getLocation({});
+
+ expect(location).toMatchObject({
+ app: '',
+ path: 'management/data/data_quality?pageState=(v:1)',
+ state: {},
+ });
+ });
+
+ it('should create a link with correct timeRange', async () => {
+ const refresh = {
+ pause: false,
+ value: 0,
+ };
+ const locatorParams = {
+ filters: {
+ timeRange: {
+ ...timeRange,
+ refresh,
+ },
+ },
+ };
+ const { datasetQualityLocator } = await setup();
+
+ const location = await datasetQualityLocator.getLocation(locatorParams);
+
+ expect(location).toMatchObject({
+ app: '',
+ path: 'management/data/data_quality?pageState=(filters:(timeRange:(from:now-30m,refresh:(pause:!f,value:0),to:now)),v:1)',
+ state: {},
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/data_quality/common/locators/types.ts b/x-pack/plugins/data_quality/common/locators/types.ts
new file mode 100644
index 0000000000000..57067cd0e482a
--- /dev/null
+++ b/x-pack/plugins/data_quality/common/locators/types.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { ManagementAppLocatorParams } from '@kbn/management-plugin/common/locator';
+import { LocatorPublic } from '@kbn/share-plugin/common';
+import { SerializableRecord } from '@kbn/utility-types';
+
+export const DATA_QUALITY_LOCATOR_ID = 'DATA_QUALITY_LOCATOR';
+
+// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+type RefreshInterval = {
+ pause: boolean;
+ value: number;
+};
+
+// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+type TimeRangeConfig = {
+ from: string;
+ to: string;
+ refresh: RefreshInterval;
+};
+
+// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+type Filters = {
+ timeRange: TimeRangeConfig;
+};
+
+export interface DataQualityLocatorParams extends SerializableRecord {
+ filters?: Filters;
+}
+
+export interface DataQualityLocatorDependencies {
+ useHash: boolean;
+ managementLocator: LocatorPublic;
+}
diff --git a/x-pack/plugins/data_quality/jest.config.js b/x-pack/plugins/data_quality/jest.config.js
new file mode 100644
index 0000000000000..15d8fe2f33986
--- /dev/null
+++ b/x-pack/plugins/data_quality/jest.config.js
@@ -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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+module.exports = {
+ preset: '@kbn/test',
+ rootDir: '../../..',
+ roots: ['/x-pack/plugins/data_quality'],
+ coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/data_quality',
+ coverageReporters: ['text', 'html'],
+ collectCoverageFrom: ['/x-pack/plugins/datas_quality/{common,public}/**/*.{ts,tsx}'],
+};
diff --git a/x-pack/plugins/data_quality/kibana.jsonc b/x-pack/plugins/data_quality/kibana.jsonc
index 2ca3c71168d52..ad1a64d4ed140 100644
--- a/x-pack/plugins/data_quality/kibana.jsonc
+++ b/x-pack/plugins/data_quality/kibana.jsonc
@@ -11,6 +11,7 @@
"datasetQuality",
"management",
"features",
+ "share",
],
"optionalPlugins": [],
"requiredBundles": [
diff --git a/x-pack/plugins/data_quality/public/plugin.ts b/x-pack/plugins/data_quality/public/plugin.ts
index 042eb05613cde..7d0893bbe4b22 100644
--- a/x-pack/plugins/data_quality/public/plugin.ts
+++ b/x-pack/plugins/data_quality/public/plugin.ts
@@ -7,6 +7,8 @@
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { ManagementAppMountParams } from '@kbn/management-plugin/public';
+import { MANAGEMENT_APP_LOCATOR } from '@kbn/deeplinks-management/constants';
+import { ManagementAppLocatorParams } from '@kbn/management-plugin/common/locator';
import {
DataQualityPluginSetup,
DataQualityPluginStart,
@@ -14,6 +16,7 @@ import {
AppPluginSetupDependencies,
} from './types';
import { PLUGIN_ID, PLUGIN_NAME } from '../common';
+import { DatasetQualityLocatorDefinition } from '../common/locators';
export class DataQualityPlugin
implements
@@ -28,7 +31,8 @@ export class DataQualityPlugin
core: CoreSetup,
plugins: AppPluginSetupDependencies
): DataQualityPluginSetup {
- const { management } = plugins;
+ const { management, share } = plugins;
+ const useHash = core.uiSettings.get('state:storeInSessionStorage');
management.sections.section.data.registerApp({
id: PLUGIN_ID,
@@ -46,6 +50,18 @@ export class DataQualityPlugin
hideFromSidebar: true,
});
+ const managementLocator =
+ share.url.locators.get(MANAGEMENT_APP_LOCATOR);
+
+ if (managementLocator) {
+ share.url.locators.create(
+ new DatasetQualityLocatorDefinition({
+ useHash,
+ managementLocator,
+ })
+ );
+ }
+
return {};
}
diff --git a/x-pack/plugins/data_quality/public/types.ts b/x-pack/plugins/data_quality/public/types.ts
index 7e695b9a20d08..279327cfe3f2a 100644
--- a/x-pack/plugins/data_quality/public/types.ts
+++ b/x-pack/plugins/data_quality/public/types.ts
@@ -7,6 +7,7 @@
import { DatasetQualityPluginStart } from '@kbn/dataset-quality-plugin/public';
import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
+import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DataQualityPluginSetup {}
@@ -16,9 +17,11 @@ export interface DataQualityPluginStart {}
export interface AppPluginSetupDependencies {
management: ManagementSetup;
+ share: SharePluginSetup;
}
export interface AppPluginStartDependencies {
datasetQuality: DatasetQualityPluginStart;
management: ManagementStart;
+ share: SharePluginStart;
}
diff --git a/x-pack/plugins/data_quality/tsconfig.json b/x-pack/plugins/data_quality/tsconfig.json
index 0ad84ba9d1dd4..59f25745ae3e6 100644
--- a/x-pack/plugins/data_quality/tsconfig.json
+++ b/x-pack/plugins/data_quality/tsconfig.json
@@ -24,6 +24,9 @@
"@kbn/i18n-react",
"@kbn/core-chrome-browser",
"@kbn/features-plugin",
+ "@kbn/share-plugin",
+ "@kbn/utility-types",
+ "@kbn/deeplinks-management",
],
"exclude": ["target/**/*"]
}
diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/common/translations.ts b/x-pack/plugins/observability_solution/observability_logs_explorer/common/translations.ts
index 0f054d6ccc705..078939b52ee16 100644
--- a/x-pack/plugins/observability_solution/observability_logs_explorer/common/translations.ts
+++ b/x-pack/plugins/observability_solution/observability_logs_explorer/common/translations.ts
@@ -57,3 +57,10 @@ export const feedbackLinkTitle = i18n.translate(
export const createSLoLabel = i18n.translate('xpack.observabilityLogsExplorer.createSlo', {
defaultMessage: 'Create SLO',
});
+
+export const datasetQualityLinkTitle = i18n.translate(
+ 'xpack.observabilityLogsExplorer.datasetQualityLinkTitle',
+ {
+ defaultMessage: 'Datasets',
+ }
+);
diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx
new file mode 100644
index 0000000000000..24782cd2ab2bb
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/dataset_quality_link.tsx
@@ -0,0 +1,98 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiHeaderLink } from '@elastic/eui';
+import { LogsExplorerPublicState } from '@kbn/logs-explorer-plugin/public';
+import { getRouterLinkProps } from '@kbn/router-utils';
+import { BrowserUrlService } from '@kbn/share-plugin/public';
+import { MatchedStateFromActor } from '@kbn/xstate-utils';
+import { useActor } from '@xstate/react';
+import React from 'react';
+import { DataQualityLocatorParams, DATA_QUALITY_LOCATOR_ID } from '@kbn/data-quality-plugin/common';
+import { datasetQualityLinkTitle } from '../../common/translations';
+import {
+ ObservabilityLogsExplorerService,
+ useObservabilityLogsExplorerPageStateContext,
+} from '../state_machines/observability_logs_explorer/src';
+import { useKibanaContextForPlugin } from '../utils/use_kibana';
+
+export const ConnectedDatasetQualityLink = React.memo(() => {
+ const {
+ services: {
+ share: { url },
+ },
+ } = useKibanaContextForPlugin();
+ const [pageState] = useActor(useObservabilityLogsExplorerPageStateContext());
+
+ if (pageState.matches({ initialized: 'validLogsExplorerState' })) {
+ return ;
+ } else {
+ return ;
+ }
+});
+
+type InitializedPageState = MatchedStateFromActor<
+ ObservabilityLogsExplorerService,
+ { initialized: 'validLogsExplorerState' }
+>;
+
+const constructLocatorParams = (
+ logsExplorerState: LogsExplorerPublicState
+): DataQualityLocatorParams => {
+ const { time, refreshInterval } = logsExplorerState;
+ const locatorParams: DataQualityLocatorParams = {
+ filters: {
+ timeRange: {
+ from: time?.from || 'now-24h',
+ to: time?.to || 'now',
+ refresh: {
+ pause: refreshInterval ? refreshInterval.pause : false,
+ value: refreshInterval ? refreshInterval.value : 60000,
+ },
+ },
+ },
+ };
+
+ return locatorParams;
+};
+
+export const DatasetQualityLink = React.memo(
+ ({
+ urlService,
+ pageState,
+ }: {
+ urlService: BrowserUrlService;
+ pageState?: InitializedPageState;
+ }) => {
+ const locator = urlService.locators.get(DATA_QUALITY_LOCATOR_ID);
+
+ const locatorParams: DataQualityLocatorParams = pageState
+ ? constructLocatorParams(pageState.context.logsExplorerState)
+ : {};
+
+ const datasetQualityUrl = locator?.getRedirectUrl(locatorParams);
+
+ const navigateToDatasetQuality = () => {
+ locator?.navigate(locatorParams);
+ };
+
+ const datasetQualityLinkProps = getRouterLinkProps({
+ href: datasetQualityUrl,
+ onClick: navigateToDatasetQuality,
+ });
+
+ return (
+
+ {datasetQualityLinkTitle}
+
+ );
+ }
+);
diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json
index f88b9de65d0c0..a3b20757c0096 100644
--- a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json
+++ b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json
@@ -50,6 +50,7 @@
"@kbn/analytics-client",
"@kbn/core-analytics-browser",
"@kbn/react-hooks",
+ "@kbn/data-quality-plugin",
],
"exclude": [
"target/**/*"
From a4577070ac42b84dada7a891a48e1977d91a9e7f Mon Sep 17 00:00:00 2001
From: Yara Tercero
Date: Fri, 31 May 2024 10:52:31 -0700
Subject: [PATCH 27/46] [Detection Engine][Cypress] - Reduce exceptions flake -
alerts_table_flow (#184383)
## Summary
Reduce flake for tests that check exceptions auto populate with alert
data. I moved to primarily checking what is being sent to the API after
the expected user interaction as a more solid way of confirming that the
flow is working as expected. Before we were checking that every value we
expect is being shown in the UI, which leads to flake depending on if
there are unexpected re-renders. There certainly are improvements to
make code side vs. test side, but this is a way to get our tests back up
and running.
---
.../auto_populate_with_alert_data.cy.ts | 357 ++++++++++--------
.../closing_all_matching_alerts.cy.ts | 25 +-
.../cypress/objects/rule.ts | 2 +-
.../cypress/tasks/exceptions.ts | 346 -----------------
.../tasks/exceptions/add_to_list_section.ts | 34 ++
.../cypress/tasks/exceptions/all_lists.ts | 27 ++
.../cypress/tasks/exceptions/comments.ts | 25 ++
.../cypress/tasks/exceptions/common.ts | 28 ++
.../cypress/tasks/exceptions/conditions.ts | 119 ++++++
.../tasks/exceptions/flyout_options.ts | 94 +++++
.../cypress/tasks/exceptions/index.ts | 13 +
11 files changed, 557 insertions(+), 513 deletions(-)
delete mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions/add_to_list_section.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions/all_lists.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions/comments.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions/common.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions/conditions.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions/flyout_options.ts
create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/exceptions/index.ts
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts
index b230c1a0a3ceb..71fea208721ee 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts
@@ -17,11 +17,7 @@ import {
addExceptionEntryFieldValueValue,
addExceptionFlyoutItemName,
submitNewExceptionItem,
- validateExceptionConditionField,
- validateExceptionCommentCountAndText,
editExceptionFlyoutItemName,
- validateHighlightedFieldsPopulatedAsExceptionConditions,
- validateEmptyExceptionConditionField,
} from '../../../../../../tasks/exceptions';
import { login } from '../../../../../../tasks/login';
import { goToExceptionsTab, visitRuleDetailsPage } from '../../../../../../tasks/rule_details';
@@ -36,160 +32,215 @@ import {
} from '../../../../../../screens/exceptions';
import { waitForAlertsToPopulate } from '../../../../../../tasks/create_new_rule';
-// TODO: https://github.com/elastic/kibana/issues/161539
-// See https://github.com/elastic/kibana/issues/163967
-describe.skip(
- 'Auto populate exception with Alert data',
- { tags: ['@ess', '@serverless', '@skipInServerless'] },
- () => {
- const ITEM_NAME = 'Sample Exception Item';
- const ITEM_NAME_EDIT = 'Sample Exception Item Edit';
- const ADDITIONAL_ENTRY = 'host.hostname';
-
- beforeEach(() => {
- cy.task('esArchiverUnload', { archiveName: 'endpoint' });
- cy.task('esArchiverLoad', { archiveName: 'endpoint' });
- login();
- createRule(getEndpointRule()).then((rule) => visitRuleDetailsPage(rule.body.id));
-
- waitForAlertsToPopulate();
- });
- after(() => {
- cy.task('esArchiverUnload', { archiveName: 'endpoint' });
- deleteAlertsAndRules();
- });
- afterEach(() => {
- cy.task('esArchiverUnload', { archiveName: 'endpoint' });
- });
-
- it('Should create a Rule exception item from alert actions overflow menu and auto populate the conditions using alert Highlighted fields', () => {
- cy.get(LOADING_INDICATOR).should('not.exist');
- addExceptionFromFirstAlert();
-
- const highlightedFieldsBasedOnAlertDoc = [
- 'host.name',
- 'agent.id',
- 'user.name',
- 'process.executable',
- 'file.path',
- ];
-
- /**
- * Validate the highlighted fields are auto populated, these
- * fields are based on the alert document that should be generated
- * when the endpoint rule runs
- */
- validateHighlightedFieldsPopulatedAsExceptionConditions(highlightedFieldsBasedOnAlertDoc);
-
- /**
- * Validate that the comments are opened by default with one comment added
- * showing a text contains information about the pre-filled conditions
- */
- validateExceptionCommentCountAndText(
- 1,
+describe('Auto populate exception with Alert data', { tags: ['@ess', '@serverless'] }, () => {
+ const ITEM_NAME = 'Sample Exception Item';
+ const ITEM_NAME_EDIT = 'Sample Exception Item Edit';
+ const ADDITIONAL_ENTRY = 'host.hostname';
+
+ beforeEach(() => {
+ cy.task('esArchiverUnload', { archiveName: 'endpoint' });
+ cy.task('esArchiverLoad', { archiveName: 'endpoint' });
+ login();
+ createRule(getEndpointRule()).then((rule) => visitRuleDetailsPage(rule.body.id));
+
+ waitForAlertsToPopulate();
+ });
+ after(() => {
+ cy.task('esArchiverUnload', { archiveName: 'endpoint' });
+ deleteAlertsAndRules();
+ });
+ afterEach(() => {
+ cy.task('esArchiverUnload', { archiveName: 'endpoint' });
+ });
+
+ it('Should create a Rule exception item from alert actions overflow menu and auto populate the conditions using alert Highlighted fields', () => {
+ cy.get(LOADING_INDICATOR).should('not.exist');
+ addExceptionFromFirstAlert();
+
+ cy.intercept('POST', '/api/detection_engine/rules/*/exceptions').as('exception_creation');
+
+ addExceptionFlyoutItemName(ITEM_NAME);
+ submitNewExceptionItem();
+
+ cy.wait('@exception_creation').then(({ response }) => {
+ cy.wrap(response?.body[0].name).should('eql', ITEM_NAME);
+ cy.wrap(response?.body[0].entries).should('eql', [
+ {
+ field: 'host.name',
+ operator: 'included',
+ type: 'match',
+ value: 'siem-kibana',
+ },
+ {
+ field: 'user.name',
+ operator: 'included',
+ type: 'match',
+ value: 'test',
+ },
+ {
+ field: 'process.executable',
+ operator: 'included',
+ type: 'match',
+ value: '/bin/zsh',
+ },
+ {
+ field: 'file.path',
+ operator: 'included',
+ type: 'match',
+ value: '123',
+ },
+ {
+ field: 'process.name',
+ operator: 'included',
+ type: 'match',
+ value: 'zsh',
+ },
+ {
+ field: 'process.args',
+ operator: 'included',
+ type: 'match_any',
+ value: ['-zsh'],
+ },
+ ]);
+ cy.wrap(response?.body[0].comments[0].comment).should(
+ 'contain',
'Exception conditions are pre-filled with relevant data from an alert with the alert id (_id):'
);
-
- addExceptionFlyoutItemName(ITEM_NAME);
- submitNewExceptionItem();
});
- it('Should create a Rule exception from Alerts take action button and change multiple exception items without resetting to initial auto-prefilled entries', () => {
- cy.get(LOADING_INDICATOR).should('not.exist');
-
- // Open first Alert Summary
- expandFirstAlert();
-
- // The Rule exception should populated with highlighted fields
- openAddRuleExceptionFromAlertActionButton();
-
- const highlightedFieldsBasedOnAlertDoc = [
- 'host.name',
- 'agent.id',
- 'user.name',
- 'process.executable',
- 'file.path',
- ];
-
- /**
- * Validate the highlighted fields are auto populated, these
- * fields are based on the alert document that should be generated
- * when the endpoint rule runs
- */
- validateHighlightedFieldsPopulatedAsExceptionConditions(highlightedFieldsBasedOnAlertDoc);
-
- /**
- * Validate that the comments are opened by default with one comment added
- * showing a text contains information about the pre-filled conditions
- */
- validateExceptionCommentCountAndText(
- 1,
+ });
+
+ it('Should create a Rule exception from Alerts take action button and change multiple exception items without resetting to initial auto-prefilled entries', () => {
+ cy.get(LOADING_INDICATOR).should('not.exist');
+
+ // Open first Alert Summary
+ expandFirstAlert();
+
+ // The Rule exception should populated with highlighted fields
+ openAddRuleExceptionFromAlertActionButton();
+
+ cy.intercept('POST', '/api/detection_engine/rules/*/exceptions').as('exception_creation');
+
+ addExceptionFlyoutItemName(ITEM_NAME);
+
+ cy.get(ADD_AND_BTN).click();
+
+ // edit conditions
+ addExceptionEntryFieldValue(ADDITIONAL_ENTRY, 5);
+ addExceptionEntryFieldValueValue('foo', 5);
+
+ // Change the name again
+ editExceptionFlyoutItemName(ITEM_NAME_EDIT);
+
+ submitNewExceptionItem();
+
+ cy.wait('@exception_creation').then(({ response }) => {
+ cy.wrap(response?.body[0].name).should('eql', ITEM_NAME_EDIT);
+ cy.wrap(response?.body[0].entries).should('eql', [
+ {
+ field: 'host.name',
+ operator: 'included',
+ type: 'match',
+ value: 'siem-kibana',
+ },
+ {
+ field: 'user.name',
+ operator: 'included',
+ type: 'match',
+ value: 'test',
+ },
+ {
+ field: 'process.executable',
+ operator: 'included',
+ type: 'match',
+ value: '/bin/zsh',
+ },
+ {
+ field: 'file.path',
+ operator: 'included',
+ type: 'match',
+ value: '123',
+ },
+ {
+ field: 'process.name',
+ operator: 'included',
+ type: 'match',
+ value: 'zsh',
+ },
+ {
+ field: 'host.hostname',
+ operator: 'included',
+ type: 'match',
+ value: 'foo',
+ },
+ ]);
+ cy.wrap(response?.body[0].comments[0].comment).should(
+ 'contain',
'Exception conditions are pre-filled with relevant data from an alert with the alert id (_id):'
);
-
- addExceptionFlyoutItemName(ITEM_NAME);
-
- cy.get(ADD_AND_BTN).click();
-
- // edit conditions
- addExceptionEntryFieldValue(ADDITIONAL_ENTRY, 5);
- addExceptionEntryFieldValueValue('foo', 5);
-
- // Change the name again
- editExceptionFlyoutItemName(ITEM_NAME_EDIT);
-
- // validate the condition is still 'host.hostname' or got rest after the name is changed
- validateExceptionConditionField(ADDITIONAL_ENTRY);
-
- submitNewExceptionItem();
-
- goToExceptionsTab();
-
- // new exception item displays
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
- cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME_EDIT);
- cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).contains('span', 'host.hostname');
});
- it('Should delete all prefilled exception entries when creating a Rule exception from Alerts take action button without resetting to initial auto-prefilled entries', () => {
- cy.get(LOADING_INDICATOR).should('not.exist');
-
- // Open first Alert Summary
- expandFirstAlert();
-
- // The Rule exception should populated with highlighted fields
- openAddRuleExceptionFromAlertActionButton();
-
- const highlightedFieldsBasedOnAlertDoc = [
- 'host.name',
- 'agent.id',
- 'user.name',
- 'process.executable',
- 'file.path',
- ];
-
- /**
- * Validate the highlighted fields are auto populated, these
- * fields are based on the alert document that should be generated
- * when the endpoint rule runs
- */
- validateHighlightedFieldsPopulatedAsExceptionConditions(highlightedFieldsBasedOnAlertDoc);
-
- /**
- * Delete all the highlighted fields to see if any condition
- * will prefuilled again.
- */
- const highlightedFieldsCount = highlightedFieldsBasedOnAlertDoc.length - 1;
- highlightedFieldsBasedOnAlertDoc.forEach((_, index) =>
- cy
- .get(ENTRY_DELETE_BTN)
- .eq(highlightedFieldsCount - index)
- .click()
- );
- /**
- * Validate that there are no highlighted fields are auto populated
- * after the deletion
- */
- validateEmptyExceptionConditionField();
+ goToExceptionsTab();
+
+ // new exception item displays
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
+ cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME_EDIT);
+ cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).contains('span', 'host.hostname');
+ });
+
+ it('Should delete all prefilled exception entries when creating a Rule exception from Alerts take action button without resetting to initial auto-prefilled entries', () => {
+ cy.get(LOADING_INDICATOR).should('not.exist');
+
+ // Open first Alert Summary
+ expandFirstAlert();
+
+ // The Rule exception should populated with highlighted fields
+ openAddRuleExceptionFromAlertActionButton();
+
+ cy.intercept('POST', '/api/detection_engine/rules/*/exceptions').as('exception_creation');
+
+ const highlightedFieldsBasedOnAlertDoc = [
+ 'host.name',
+ 'agent.id',
+ 'user.name',
+ 'process.executable',
+ 'file.path',
+ ];
+
+ /**
+ * Delete all the highlighted fields to see if any condition
+ * will prefuilled again.
+ */
+ const highlightedFieldsCount = highlightedFieldsBasedOnAlertDoc.length - 1;
+ highlightedFieldsBasedOnAlertDoc.forEach((_, index) =>
+ cy
+ .get(ENTRY_DELETE_BTN)
+ .eq(highlightedFieldsCount - index)
+ .click()
+ );
+
+ // add condition - should be the only condition now
+ addExceptionEntryFieldValue(ADDITIONAL_ENTRY, 0);
+ addExceptionEntryFieldValueValue('foo', 0);
+
+ // Add name that is required to save
+ editExceptionFlyoutItemName(ITEM_NAME);
+
+ submitNewExceptionItem();
+
+ cy.wait('@exception_creation').then(({ response }) => {
+ cy.wrap(response?.body[0].name).should('eql', ITEM_NAME);
+ cy.wrap(response?.body[0].entries).should('eql', [
+ {
+ field: 'host.hostname',
+ operator: 'included',
+ type: 'match',
+ value: 'foo',
+ },
+ ]);
+ cy.wrap(response?.body[0].comments[0].comment).should(
+ 'contain',
+ 'Exception conditions are pre-filled with relevant data from an alert with the alert id (_id):'
+ );
});
- }
-);
+ });
+});
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts
index 6806ad1c776c1..50284ea2cae94 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts
@@ -12,7 +12,7 @@ import {
} from '../../../../../../tasks/alerts';
import { deleteAlertsAndRules, postDataView } from '../../../../../../tasks/api_calls/common';
import { login } from '../../../../../../tasks/login';
-import { visitRuleDetailsPage } from '../../../../../../tasks/rule_details';
+import { clickDisableRuleSwitch, visitRuleDetailsPage } from '../../../../../../tasks/rule_details';
import { createRule } from '../../../../../../tasks/api_calls/rules';
import { getNewRule } from '../../../../../../objects/rule';
import { LOADING_INDICATOR } from '../../../../../../screens/security_header';
@@ -26,14 +26,13 @@ import {
submitNewExceptionItem,
} from '../../../../../../tasks/exceptions';
-// TODO: https://github.com/elastic/kibana/issues/161539
-// See https://github.com/elastic/kibana/issues/163967
-describe('Close matching Alerts ', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => {
+describe('Close matching Alerts ', { tags: ['@ess', '@serverless'] }, () => {
const ITEM_NAME = 'Sample Exception Item';
beforeEach(() => {
- cy.task('esArchiverUnload', { archiveName: 'exceptions' });
deleteAlertsAndRules();
+
+ cy.task('esArchiverUnload', { archiveName: 'exceptions' });
cy.task('esArchiverLoad', { archiveName: 'exceptions' });
login();
@@ -42,20 +41,22 @@ describe('Close matching Alerts ', { tags: ['@ess', '@serverless', '@skipInServe
getNewRule({
query: 'agent.name:*',
data_view_id: 'exceptions-*',
- interval: '10s',
+ interval: '1m',
rule_id: 'rule_testing',
})
).then((rule) => visitRuleDetailsPage(rule.body.id));
waitForAlertsToPopulate();
+ // Disables enabled rule
+ clickDisableRuleSwitch();
+ cy.get(LOADING_INDICATOR).should('not.exist');
});
after(() => {
cy.task('esArchiverUnload', { archiveName: 'exceptions' });
+ deleteAlertsAndRules();
});
- // TODO: https://github.com/elastic/kibana/issues/161539
- it.skip('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => {
- cy.get(LOADING_INDICATOR).should('not.exist');
+ it('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => {
addExceptionFromFirstAlert();
addExceptionEntryFieldValue('agent.name', 0);
@@ -73,9 +74,7 @@ describe('Close matching Alerts ', { tags: ['@ess', '@serverless', '@skipInServe
// Closed alert should appear in table
goToClosedAlertsOnRuleDetailsPage();
- // We should not expect a specific number using should "have.text" because as the Rule is executing it is highly likely to create other
- // alerts and when the exception conditions start to close matching alerts there might be more than what was
- // before creating an exception
- cy.get(ALERTS_COUNT).should('exist');
+ cy.get(LOADING_INDICATOR).should('not.exist');
+ cy.get(ALERTS_COUNT).should('contain', '1');
});
});
diff --git a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts
index dd0e63379054e..7260dae14d930 100644
--- a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts
+++ b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts
@@ -635,7 +635,7 @@ export const getEndpointRule = (): QueryRuleCreateProps => ({
description: 'The new rule description.',
severity: 'high',
risk_score: 17,
- interval: '10s',
+ interval: '1m',
from: 'now-50000h',
max_signals: 100,
exceptions_list: [
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts
deleted file mode 100644
index db260fab5ad6b..0000000000000
--- a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts
+++ /dev/null
@@ -1,346 +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 { Exception } from '../objects/exception';
-import { TOASTER_CLOSE_ICON } from '../screens/alerts_detection_rules';
-import {
- FIELD_INPUT,
- OPERATOR_INPUT,
- CANCEL_BTN,
- EXCEPTION_ITEM_CONTAINER,
- EXCEPTION_FLYOUT_TITLE,
- VALUES_INPUT,
- VALUES_MATCH_ANY_INPUT,
- EXCEPTION_EDIT_FLYOUT_SAVE_BTN,
- CLOSE_ALERTS_CHECKBOX,
- CONFIRM_BTN,
- EXCEPTION_ITEM_NAME_INPUT,
- CLOSE_SINGLE_ALERT_CHECKBOX,
- ADD_TO_RULE_RADIO_LABEL,
- ADD_TO_SHARED_LIST_RADIO_LABEL,
- SHARED_LIST_SWITCH,
- OS_SELECTION_SECTION,
- OS_INPUT,
- EXCEPTION_FIELD_MAPPING_CONFLICTS_ICON,
- EXCEPTION_FIELD_MAPPING_CONFLICTS_TOOLTIP,
- EXCEPTION_FIELD_MAPPING_CONFLICTS_ACCORDION_ICON,
- EXCEPTION_FIELD_MAPPING_CONFLICTS_DESCRIPTION,
- EXCEPTION_COMMENT_TEXT_AREA,
- EXCEPTION_COMMENTS_ACCORDION_BTN,
- EXCEPTION_ITEM_VIEWER_CONTAINER_SHOW_COMMENTS_BTN,
- EXCEPTION_ITEM_COMMENTS_CONTAINER,
- EXCEPTION_ITEM_COMMENT_COPY_BTN,
- VALUES_MATCH_INCLUDED_INPUT,
- EXCEPTION_ITEM_VIEWER_CONTAINER,
- EXCEPTION_CARD_ITEM_AFFECTED_RULES,
- EXCEPTION_CARD_ITEM_AFFECTED_RULES_MENU_ITEM,
- ADD_AND_BTN,
- ADD_OR_BTN,
- RULE_ACTION_LINK_RULE_SWITCH,
- LINK_TO_SHARED_LIST_RADIO,
- EXCEPTION_ITEM_HEADER_ACTION_MENU,
- EXCEPTION_ITEM_OVERFLOW_ACTION_EDIT,
- EXCEPTION_ITEM_OVERFLOW_ACTION_DELETE,
- EXCEPTIONS_ITEM_ERROR_CALLOUT,
- EXCEPTIONS_ITEM_ERROR_DISMISS_BUTTON,
-} from '../screens/exceptions';
-import { closeErrorToast } from './alerts_detection_rules';
-
-export const assertNumberOfExceptionItemsExists = (numberOfItems: number) => {
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', numberOfItems);
-};
-
-export const expectToContainItem = (container: string, itemName: string) => {
- cy.log(`Expecting exception items table to contain '${itemName}'`);
- cy.get(container).should('include.text', itemName);
-};
-
-export const assertExceptionItemsExists = (container: string, itemNames: string[]) => {
- for (const itemName of itemNames) {
- expectToContainItem(container, itemName);
- }
-};
-
-export const addExceptionEntryFieldValueOfItemX = (
- field: string,
- itemIndex = 0,
- fieldIndex = 0
-) => {
- cy.get(EXCEPTION_ITEM_CONTAINER)
- .eq(itemIndex)
- .find(FIELD_INPUT)
- .eq(fieldIndex)
- .type(`{selectall}${field}{enter}`);
- cy.get(EXCEPTION_FLYOUT_TITLE).click();
-};
-
-export const searchExceptionEntryFieldWithPrefix = (fieldPrefix: string, index = 0) => {
- cy.get(FIELD_INPUT).eq(index).click({ force: true });
- cy.get(FIELD_INPUT).eq(index).type(fieldPrefix);
-};
-
-export const showFieldConflictsWarningTooltipWithMessage = (message: string, index = 0) => {
- cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_ICON).eq(index).realHover();
- cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_TOOLTIP).should('be.visible');
- cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_TOOLTIP).should('have.text', message);
-};
-
-export const showMappingConflictsWarningMessage = (message: string, index = 0) => {
- cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_ACCORDION_ICON).eq(index).click({ force: true });
- cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_DESCRIPTION).eq(index).should('have.text', message);
-};
-
-export const selectCurrentEntryField = (index = 0) => {
- cy.get(FIELD_INPUT).eq(index).type(`{downarrow}{enter}`);
-};
-
-export const addExceptionEntryFieldValue = (field: string, index = 0) => {
- cy.get(FIELD_INPUT).eq(index).type(`{selectall}${field}{enter}`);
- cy.get(EXCEPTION_FLYOUT_TITLE).click();
-};
-
-export const addExceptionEntryFieldValueAndSelectSuggestion = (field: string, index = 0) => {
- cy.get(FIELD_INPUT).eq(index).type(`${field}`);
- cy.get(`button[title="${field}"]`).click();
-};
-
-export const addExceptionEntryOperatorValue = (operator: string, index = 0) => {
- cy.get(OPERATOR_INPUT).eq(index).type(`{selectall}${operator}{enter}`);
- cy.get(EXCEPTION_FLYOUT_TITLE).click();
-};
-
-export const addExceptionEntryFieldValueValue = (value: string, index = 0) => {
- cy.get(VALUES_INPUT).eq(index).type(`{selectall}${value}{enter}`);
- cy.get(EXCEPTION_FLYOUT_TITLE).click();
-};
-
-export const addExceptionEntryFieldMatchAnyValue = (values: string[], index = 0) => {
- values.forEach((value) => {
- cy.get(VALUES_MATCH_ANY_INPUT).eq(index).type(`{selectall}${value}{enter}`);
- });
- cy.get(EXCEPTION_FLYOUT_TITLE).click();
-};
-export const addExceptionEntryFieldMatchIncludedValue = (value: string, index = 0) => {
- cy.get(VALUES_MATCH_INCLUDED_INPUT).eq(index).type(`{selectall}${value}{enter}`);
- cy.get(EXCEPTION_FLYOUT_TITLE).click();
-};
-
-export const closeExceptionBuilderFlyout = () => {
- cy.get(CANCEL_BTN).click();
-};
-
-export const editException = (updatedField: string, itemIndex = 0, fieldIndex = 0) => {
- addExceptionEntryFieldValueOfItemX(`${updatedField}{downarrow}{enter}`, itemIndex, fieldIndex);
- addExceptionEntryFieldValueValue('foo', itemIndex);
-};
-
-export const addExceptionFlyoutItemName = (name: string) => {
- // waitUntil reduces the flakiness of this task because sometimes
- // there are background process/events happening which prevents cypress
- // to completely write the name of the exception before it page re-renders
- // thereby cypress losing the focus on the input element.
- cy.waitUntil(() => cy.get(EXCEPTION_ITEM_NAME_INPUT).then(($el) => Cypress.dom.isAttached($el)));
- cy.get(EXCEPTION_ITEM_NAME_INPUT).should('exist');
- cy.get(EXCEPTION_ITEM_NAME_INPUT).scrollIntoView();
- cy.get(EXCEPTION_ITEM_NAME_INPUT).should('be.visible');
- cy.get(EXCEPTION_ITEM_NAME_INPUT).first().focus();
- cy.get(EXCEPTION_ITEM_NAME_INPUT).type(`{selectall}${name}{enter}`, { force: true });
- cy.get(EXCEPTION_ITEM_NAME_INPUT).should('have.value', name);
-};
-
-export const editExceptionFlyoutItemName = (name: string) => {
- cy.get(EXCEPTION_ITEM_NAME_INPUT).clear();
- cy.get(EXCEPTION_ITEM_NAME_INPUT).type(`{selectall}${name}{enter}`);
- cy.get(EXCEPTION_ITEM_NAME_INPUT).should('have.value', name);
-};
-
-export const selectBulkCloseAlerts = () => {
- cy.get(CLOSE_ALERTS_CHECKBOX).should('exist');
- cy.get(CLOSE_ALERTS_CHECKBOX).click({ force: true });
-};
-
-export const selectCloseSingleAlerts = () => {
- cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).click({ force: true });
-};
-
-export const addExceptionConditions = (exception: Exception) => {
- cy.get(FIELD_INPUT).type(`${exception.field}{downArrow}{enter}`);
- cy.get(OPERATOR_INPUT).type(`{selectall}${exception.operator}{enter}`);
- if (exception.operator === 'is one of') {
- addExceptionEntryFieldMatchAnyValue(exception.values, 0);
- } else {
- exception.values.forEach((value) => {
- cy.get(VALUES_INPUT).type(`{selectall}${value}{enter}`);
- });
- }
-};
-
-export const validateExceptionConditionField = (value: string) => {
- cy.get(EXCEPTION_ITEM_CONTAINER).contains('span', value);
-};
-export const validateEmptyExceptionConditionField = () => {
- cy.get(FIELD_INPUT).should('be.empty');
-};
-export const submitNewExceptionItem = () => {
- cy.get(CONFIRM_BTN).should('exist');
- /* Sometimes a toaster error message unrelated with the test performed is displayed.
- The toaster is blocking the confirm button we have to click. Using force true would solve the issue, but should not be used.
- There are some tests that use the closeErrorToast() method to close error toasters before continuing with the interactions with the page.
- In this case we check if a toaster is displayed and if so, close it to continue with the test.
- */
- cy.root().then(($page) => {
- const element = $page.find(TOASTER_CLOSE_ICON);
- if (element.length > 0) {
- closeErrorToast();
- }
- });
- cy.get(CONFIRM_BTN).click();
- cy.get(CONFIRM_BTN).should('not.exist');
-};
-
-export const submitNewExceptionItemWithFailure = () => {
- cy.get(CONFIRM_BTN).should('exist');
- cy.get(CONFIRM_BTN).click();
- cy.get(CONFIRM_BTN).should('exist');
-};
-
-export const submitEditedExceptionItem = () => {
- cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).click();
- cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('not.exist');
-};
-
-export const selectAddToRuleRadio = () => {
- cy.get(ADD_TO_RULE_RADIO_LABEL).click();
-};
-
-export const selectSharedListToAddExceptionTo = (numListsToCheck = 1) => {
- cy.get(ADD_TO_SHARED_LIST_RADIO_LABEL).click();
- for (let i = 0; i < numListsToCheck; i++) {
- cy.get(SHARED_LIST_SWITCH).eq(i).click();
- }
-};
-
-export const selectOs = (os: string) => {
- cy.get(OS_SELECTION_SECTION).should('exist');
- cy.get(OS_INPUT).type(`${os}{downArrow}{enter}`);
-};
-
-export const addExceptionComment = (comment: string) => {
- cy.get(EXCEPTION_COMMENTS_ACCORDION_BTN).click();
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(`${comment}`);
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).should('have.value', comment);
-};
-
-export const addExceptionHugeComment = (comment: string) => {
- cy.get(EXCEPTION_COMMENTS_ACCORDION_BTN).click();
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(` {backspace}`);
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).invoke('val', comment);
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(` {backspace}`);
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).should('have.value', comment);
-};
-
-export const editExceptionComment = (comment: string) => {
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).clear();
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(`${comment}`);
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).should('have.value', comment);
-};
-
-export const validateExceptionCommentCountAndText = (count: number, comment: string) => {
- cy.get(EXCEPTION_COMMENTS_ACCORDION_BTN).contains('h3', count);
- cy.get(EXCEPTION_COMMENT_TEXT_AREA).contains('textarea', comment);
-};
-export const clickOnShowComments = () => {
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER_SHOW_COMMENTS_BTN).click();
-};
-
-export const clickCopyCommentToClipboard = () => {
- // Disable window prompt which is used in link creation by copy-to-clipboard library
- // as this prompt pauses test execution during `cypress open`
- cy.window().then((win) => {
- cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
- });
- cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER).first().find(EXCEPTION_ITEM_COMMENT_COPY_BTN).click();
-};
-
-export const validateExceptionItemAffectsTheCorrectRulesInRulePage = (rulesCount: number) => {
- cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', rulesCount);
- cy.get(EXCEPTION_CARD_ITEM_AFFECTED_RULES).should('have.text', `Affects ${rulesCount} rule`);
-};
-export const validateExceptionItemFirstAffectedRuleNameInRulePage = (ruleName: string) => {
- cy.get(EXCEPTION_CARD_ITEM_AFFECTED_RULES).click();
-
- cy.get(EXCEPTION_CARD_ITEM_AFFECTED_RULES_MENU_ITEM).first().should('have.text', ruleName);
-};
-
-export const addTwoAndedConditions = (
- firstEntryField: string,
- firstEntryFieldValue: string,
- secondEntryField: string,
- secondEntryFieldValue: string
-) => {
- addExceptionEntryFieldValue(firstEntryField, 0);
- addExceptionEntryFieldValueValue(firstEntryFieldValue, 0);
-
- cy.get(ADD_AND_BTN).click();
-
- addExceptionEntryFieldValue(secondEntryField, 1);
- addExceptionEntryFieldValueValue(secondEntryFieldValue, 1);
-};
-
-export const addTwoORedConditions = (
- firstEntryField: string,
- firstEntryFieldValue: string,
- secondEntryField: string,
- secondEntryFieldValue: string
-) => {
- addExceptionEntryFieldValue(firstEntryField, 0);
- addExceptionEntryFieldValueValue(firstEntryFieldValue, 0);
-
- cy.get(ADD_OR_BTN).click();
-
- addExceptionEntryFieldValue(secondEntryField, 1);
- addExceptionEntryFieldValueValue(secondEntryFieldValue, 1);
-};
-
-export const linkFirstRuleOnExceptionFlyout = () => {
- cy.get(RULE_ACTION_LINK_RULE_SWITCH).find('button').click();
-};
-
-export const linkFirstSharedListOnExceptionFlyout = () => {
- cy.get(LINK_TO_SHARED_LIST_RADIO).click();
- cy.get(RULE_ACTION_LINK_RULE_SWITCH).find('button').click();
-};
-
-export const editFirstExceptionItemInListDetailPage = () => {
- // Click on the first exception overflow menu items
- cy.get(EXCEPTION_ITEM_HEADER_ACTION_MENU).click();
-
- // Open the edit modal
- cy.get(EXCEPTION_ITEM_OVERFLOW_ACTION_EDIT).click();
-};
-export const deleteFirstExceptionItemInListDetailPage = () => {
- // Click on the first exception overflow menu items
- cy.get(EXCEPTION_ITEM_HEADER_ACTION_MENU).click();
-
- // Delete exception
- cy.get(EXCEPTION_ITEM_OVERFLOW_ACTION_DELETE).click();
-};
-export const validateHighlightedFieldsPopulatedAsExceptionConditions = (
- highlightedFields: string[]
-) => {
- return highlightedFields.every((field) => validateExceptionConditionField(field));
-};
-
-export const dismissExceptionItemErrorCallOut = () => {
- cy.get(EXCEPTIONS_ITEM_ERROR_CALLOUT).should(
- 'include.text',
- 'An error occured submitting exception'
- );
-
- // Click dismiss button
- cy.get(EXCEPTIONS_ITEM_ERROR_DISMISS_BUTTON).click();
-};
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/add_to_list_section.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/add_to_list_section.ts
new file mode 100644
index 0000000000000..6a56ee778bd1a
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/add_to_list_section.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ ADD_TO_RULE_RADIO_LABEL,
+ ADD_TO_SHARED_LIST_RADIO_LABEL,
+ LINK_TO_SHARED_LIST_RADIO,
+ RULE_ACTION_LINK_RULE_SWITCH,
+ SHARED_LIST_SWITCH,
+} from '../../screens/exceptions';
+
+export const selectAddToRuleRadio = () => {
+ cy.get(ADD_TO_RULE_RADIO_LABEL).click();
+};
+
+export const selectSharedListToAddExceptionTo = (numListsToCheck = 1) => {
+ cy.get(ADD_TO_SHARED_LIST_RADIO_LABEL).click();
+ for (let i = 0; i < numListsToCheck; i++) {
+ cy.get(SHARED_LIST_SWITCH).eq(i).click();
+ }
+};
+
+export const linkFirstRuleOnExceptionFlyout = () => {
+ cy.get(RULE_ACTION_LINK_RULE_SWITCH).find('button').click();
+};
+
+export const linkFirstSharedListOnExceptionFlyout = () => {
+ cy.get(LINK_TO_SHARED_LIST_RADIO).click();
+ cy.get(RULE_ACTION_LINK_RULE_SWITCH).find('button').click();
+};
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/all_lists.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/all_lists.ts
new file mode 100644
index 0000000000000..d68a3fd550223
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/all_lists.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 {
+ EXCEPTION_ITEM_HEADER_ACTION_MENU,
+ EXCEPTION_ITEM_OVERFLOW_ACTION_DELETE,
+ EXCEPTION_ITEM_OVERFLOW_ACTION_EDIT,
+} from '../../screens/exceptions';
+
+export const editFirstExceptionItemInListDetailPage = () => {
+ // Click on the first exception overflow menu items
+ cy.get(EXCEPTION_ITEM_HEADER_ACTION_MENU).click();
+
+ // Open the edit modal
+ cy.get(EXCEPTION_ITEM_OVERFLOW_ACTION_EDIT).click();
+};
+export const deleteFirstExceptionItemInListDetailPage = () => {
+ // Click on the first exception overflow menu items
+ cy.get(EXCEPTION_ITEM_HEADER_ACTION_MENU).click();
+
+ // Delete exception
+ cy.get(EXCEPTION_ITEM_OVERFLOW_ACTION_DELETE).click();
+};
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/comments.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/comments.ts
new file mode 100644
index 0000000000000..8aeedcbe74f8e
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/comments.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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ EXCEPTION_COMMENTS_ACCORDION_BTN,
+ EXCEPTION_COMMENT_TEXT_AREA,
+} from '../../screens/exceptions';
+
+export const addExceptionHugeComment = (comment: string) => {
+ cy.get(EXCEPTION_COMMENTS_ACCORDION_BTN).click();
+ cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(` {backspace}`);
+ cy.get(EXCEPTION_COMMENT_TEXT_AREA).invoke('val', comment);
+ cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(` {backspace}`);
+ cy.get(EXCEPTION_COMMENT_TEXT_AREA).should('have.value', comment);
+};
+
+export const editExceptionComment = (comment: string) => {
+ cy.get(EXCEPTION_COMMENT_TEXT_AREA).clear();
+ cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(`${comment}`);
+ cy.get(EXCEPTION_COMMENT_TEXT_AREA).should('have.value', comment);
+};
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/common.ts
new file mode 100644
index 0000000000000..197e1db4d54ab
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/common.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 { EXCEPTION_ITEM_VIEWER_CONTAINER, FIELD_INPUT } from '../../screens/exceptions';
+
+export const assertNumberOfExceptionItemsExists = (numberOfItems: number) => {
+ cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', numberOfItems);
+};
+
+export const expectToContainItem = (container: string, itemName: string) => {
+ cy.log(`Expecting exception items table to contain '${itemName}'`);
+ cy.get(container).should('include.text', itemName);
+};
+
+export const assertExceptionItemsExists = (container: string, itemNames: string[]) => {
+ for (const itemName of itemNames) {
+ expectToContainItem(container, itemName);
+ }
+};
+
+export const searchExceptionEntryFieldWithPrefix = (fieldPrefix: string, index = 0) => {
+ cy.get(FIELD_INPUT).eq(index).click({ force: true });
+ cy.get(FIELD_INPUT).eq(index).type(fieldPrefix);
+};
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/conditions.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/conditions.ts
new file mode 100644
index 0000000000000..f78c23b0f0196
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/conditions.ts
@@ -0,0 +1,119 @@
+/*
+ * 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 { Exception } from '../../objects/exception';
+import {
+ FIELD_INPUT,
+ OPERATOR_INPUT,
+ EXCEPTION_ITEM_CONTAINER,
+ EXCEPTION_FLYOUT_TITLE,
+ VALUES_INPUT,
+ VALUES_MATCH_ANY_INPUT,
+ VALUES_MATCH_INCLUDED_INPUT,
+ ADD_AND_BTN,
+ ADD_OR_BTN,
+} from '../../screens/exceptions';
+
+export const addExceptionEntryFieldValueOfItemX = (
+ field: string,
+ itemIndex = 0,
+ fieldIndex = 0
+) => {
+ cy.get(EXCEPTION_ITEM_CONTAINER)
+ .eq(itemIndex)
+ .find(FIELD_INPUT)
+ .eq(fieldIndex)
+ .type(`{selectall}${field}{enter}`);
+ cy.get(EXCEPTION_FLYOUT_TITLE).click();
+};
+
+export const selectCurrentEntryField = (index = 0) => {
+ cy.get(FIELD_INPUT).eq(index).type(`{downarrow}{enter}`);
+};
+
+export const addExceptionEntryFieldValue = (field: string, index = 0) => {
+ cy.get(FIELD_INPUT).eq(index).type(`{selectall}${field}{enter}`);
+ cy.get(EXCEPTION_FLYOUT_TITLE).click();
+};
+
+export const addExceptionEntryFieldValueAndSelectSuggestion = (field: string, index = 0) => {
+ cy.get(FIELD_INPUT).eq(index).type(`${field}`);
+ cy.get(`button[title="${field}"]`).click();
+};
+
+export const addExceptionEntryOperatorValue = (operator: string, index = 0) => {
+ cy.get(OPERATOR_INPUT).eq(index).type(`{selectall}${operator}{enter}`);
+ cy.get(EXCEPTION_FLYOUT_TITLE).click();
+};
+
+export const addExceptionEntryFieldValueValue = (value: string, index = 0) => {
+ cy.get(VALUES_INPUT).eq(index).type(`{selectall}${value}{enter}`);
+ cy.get(EXCEPTION_FLYOUT_TITLE).click();
+};
+
+export const addExceptionEntryFieldMatchAnyValue = (values: string[], index = 0) => {
+ values.forEach((value) => {
+ cy.get(VALUES_MATCH_ANY_INPUT).eq(index).type(`{selectall}${value}{enter}`);
+ });
+ cy.get(EXCEPTION_FLYOUT_TITLE).click();
+};
+
+export const addExceptionEntryFieldMatchIncludedValue = (value: string, index = 0) => {
+ cy.get(VALUES_MATCH_INCLUDED_INPUT).eq(index).type(`{selectall}${value}{enter}`);
+ cy.get(EXCEPTION_FLYOUT_TITLE).click();
+};
+
+export const addExceptionConditions = (exception: Exception) => {
+ cy.get(FIELD_INPUT).type(`${exception.field}{downArrow}{enter}`);
+ cy.get(OPERATOR_INPUT).type(`{selectall}${exception.operator}{enter}`);
+ if (exception.operator === 'is one of') {
+ addExceptionEntryFieldMatchAnyValue(exception.values, 0);
+ } else {
+ exception.values.forEach((value) => {
+ cy.get(VALUES_INPUT).type(`{selectall}${value}{enter}`);
+ });
+ }
+};
+
+export const validateExceptionConditionField = (value: string) => {
+ cy.get(EXCEPTION_ITEM_CONTAINER).contains('span', value);
+};
+
+export const addTwoAndedConditions = (
+ firstEntryField: string,
+ firstEntryFieldValue: string,
+ secondEntryField: string,
+ secondEntryFieldValue: string
+) => {
+ addExceptionEntryFieldValue(firstEntryField, 0);
+ addExceptionEntryFieldValueValue(firstEntryFieldValue, 0);
+
+ cy.get(ADD_AND_BTN).click();
+
+ addExceptionEntryFieldValue(secondEntryField, 1);
+ addExceptionEntryFieldValueValue(secondEntryFieldValue, 1);
+};
+
+export const addTwoORedConditions = (
+ firstEntryField: string,
+ firstEntryFieldValue: string,
+ secondEntryField: string,
+ secondEntryFieldValue: string
+) => {
+ addExceptionEntryFieldValue(firstEntryField, 0);
+ addExceptionEntryFieldValueValue(firstEntryFieldValue, 0);
+
+ cy.get(ADD_OR_BTN).click();
+
+ addExceptionEntryFieldValue(secondEntryField, 1);
+ addExceptionEntryFieldValueValue(secondEntryFieldValue, 1);
+};
+
+export const editException = (updatedField: string, itemIndex = 0, fieldIndex = 0) => {
+ addExceptionEntryFieldValueOfItemX(`${updatedField}{downarrow}{enter}`, itemIndex, fieldIndex);
+ addExceptionEntryFieldValueValue('foo', itemIndex);
+};
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/flyout_options.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/flyout_options.ts
new file mode 100644
index 0000000000000..36d98573e370b
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/flyout_options.ts
@@ -0,0 +1,94 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { TOASTER_CLOSE_ICON } from '../../screens/alerts_detection_rules';
+import {
+ CANCEL_BTN,
+ CLOSE_ALERTS_CHECKBOX,
+ CLOSE_SINGLE_ALERT_CHECKBOX,
+ CONFIRM_BTN,
+ EXCEPTION_EDIT_FLYOUT_SAVE_BTN,
+ EXCEPTION_FIELD_MAPPING_CONFLICTS_ACCORDION_ICON,
+ EXCEPTION_FIELD_MAPPING_CONFLICTS_DESCRIPTION,
+ EXCEPTION_FIELD_MAPPING_CONFLICTS_ICON,
+ EXCEPTION_FIELD_MAPPING_CONFLICTS_TOOLTIP,
+ EXCEPTION_ITEM_NAME_INPUT,
+ OS_INPUT,
+ OS_SELECTION_SECTION,
+} from '../../screens/exceptions';
+import { closeErrorToast } from '../alerts_detection_rules';
+
+export const showFieldConflictsWarningTooltipWithMessage = (message: string, index = 0) => {
+ cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_ICON).eq(index).realHover();
+ cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_TOOLTIP).should('be.visible');
+ cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_TOOLTIP).should('have.text', message);
+};
+
+export const showMappingConflictsWarningMessage = (message: string, index = 0) => {
+ cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_ACCORDION_ICON).eq(index).click({ force: true });
+ cy.get(EXCEPTION_FIELD_MAPPING_CONFLICTS_DESCRIPTION).eq(index).should('have.text', message);
+};
+
+export const addExceptionFlyoutItemName = (name: string) => {
+ // waitUntil reduces the flakiness of this task because sometimes
+ // there are background process/events happening which prevents cypress
+ // to completely write the name of the exception before it page re-renders
+ // thereby cypress losing the focus on the input element.
+ cy.waitUntil(() => cy.get(EXCEPTION_ITEM_NAME_INPUT).then(($el) => Cypress.dom.isAttached($el)));
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).should('exist');
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).scrollIntoView();
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).should('be.visible');
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).first().focus();
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).type(`{selectall}${name}{enter}`, { force: true });
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).should('have.value', name);
+};
+
+export const editExceptionFlyoutItemName = (name: string) => {
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).clear();
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).type(`{selectall}${name}{enter}`);
+ cy.get(EXCEPTION_ITEM_NAME_INPUT).should('have.value', name);
+};
+
+export const selectBulkCloseAlerts = () => {
+ cy.get(CLOSE_ALERTS_CHECKBOX).should('exist');
+ cy.get(CLOSE_ALERTS_CHECKBOX).click({ force: true });
+};
+
+export const selectCloseSingleAlerts = () => {
+ cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).click({ force: true });
+};
+
+export const closeExceptionBuilderFlyout = () => {
+ cy.get(CANCEL_BTN).click();
+};
+
+export const selectOs = (os: string) => {
+ cy.get(OS_SELECTION_SECTION).should('exist');
+ cy.get(OS_INPUT).type(`${os}{downArrow}{enter}`);
+};
+
+export const submitNewExceptionItem = () => {
+ cy.get(CONFIRM_BTN).should('exist');
+ /* Sometimes a toaster error message unrelated with the test performed is displayed.
+ The toaster is blocking the confirm button we have to click. Using force true would solve the issue, but should not be used.
+ There are some tests that use the closeErrorToast() method to close error toasters before continuing with the interactions with the page.
+ In this case we check if a toaster is displayed and if so, close it to continue with the test.
+ */
+ cy.root().then(($page) => {
+ const element = $page.find(TOASTER_CLOSE_ICON);
+ if (element.length > 0) {
+ closeErrorToast();
+ }
+ });
+ cy.get(CONFIRM_BTN).click();
+ cy.get(CONFIRM_BTN).should('not.exist');
+};
+
+export const submitEditedExceptionItem = () => {
+ cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).click();
+ cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).should('not.exist');
+};
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/index.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/index.ts
new file mode 100644
index 0000000000000..71368362dbd6d
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './common';
+export * from './conditions';
+export * from './comments';
+export * from './all_lists';
+export * from './add_to_list_section';
+export * from './flyout_options';
From 3f3185dea93dab43b2318611ee109b0b957c852d Mon Sep 17 00:00:00 2001
From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com>
Date: Fri, 31 May 2024 14:00:11 -0400
Subject: [PATCH 28/46] [Security Solution] Fix cell popover rendering when
opened from unified datatable (#184143)
## Summary
Fixes an issue where the timeline custom cell popovers with unified
datatable components were 1) not being passed through to EuiDataGrid and
2) had the same z-index as the flyout, and so the popover is not
visible. Fixes the issue by adding a z-index only to the table within
the timeline portal and passing the renderCellPopover prop through to
the eui component.
![flyout_unified_working](https://github.com/elastic/kibana/assets/56408403/01969cd1-914e-40bd-a07f-417c7025db2d)
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
https://github.com/elastic/kibana/issues/182813
---------
Co-authored-by: Jatin Kathuria
---
.../src/components/data_table.tsx | 14 ++++++
.../utils/get_render_cell_popover.test.tsx | 47 +++++++++++++++++++
.../src/utils/get_render_cell_popover.tsx | 33 +++++++++++++
.../src/utils/get_render_cell_value.tsx | 11 +++--
.../unified_components/data_table/index.tsx | 7 ++-
.../timeline/unified_components/styles.tsx | 8 +++-
6 files changed, 114 insertions(+), 6 deletions(-)
create mode 100644 packages/kbn-unified-data-table/src/utils/get_render_cell_popover.test.tsx
create mode 100644 packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx
diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx
index 7dbc3a4659595..9c9b6e2ccd915 100644
--- a/packages/kbn-unified-data-table/src/components/data_table.tsx
+++ b/packages/kbn-unified-data-table/src/components/data_table.tsx
@@ -84,6 +84,7 @@ import { useRowHeight } from '../hooks/use_row_height';
import { CompareDocuments } from './compare_documents';
import { useFullScreenWatcher } from '../hooks/use_full_screen_watcher';
import { UnifiedDataTableRenderCustomToolbar } from './custom_toolbar/render_custom_toolbar';
+import { getCustomCellPopoverRenderer } from '../utils/get_render_cell_popover';
export type SortOrder = [string, string];
@@ -375,6 +376,12 @@ export interface UnifiedDataTableProps {
* Optional extra props passed to the renderCellValue function/component.
*/
cellContext?: EuiDataGridProps['cellContext'];
+ /**
+ *
+ * Custom cell Popover Render Component.
+ *
+ */
+ renderCellPopover?: EuiDataGridProps['renderCellPopover'];
}
export const EuiDataGridMemoized = React.memo(EuiDataGrid);
@@ -443,6 +450,7 @@ export const UnifiedDataTable = ({
customControlColumnsConfiguration,
enableComparisonMode,
cellContext,
+ renderCellPopover,
}: UnifiedDataTableProps) => {
const { fieldFormats, toastNotifications, dataViewFieldEditor, uiSettings, storage, data } =
services;
@@ -614,6 +622,11 @@ export const UnifiedDataTable = ({
]
);
+ const renderCustomPopover = useMemo(
+ () => renderCellPopover ?? getCustomCellPopoverRenderer(),
+ [renderCellPopover]
+ );
+
/**
* Render variables
*/
@@ -1063,6 +1076,7 @@ export const UnifiedDataTable = ({
renderCustomToolbar={renderCustomToolbarFn}
trailingControlColumns={customTrailingControlColumn}
cellContext={cellContext}
+ renderCellPopover={renderCustomPopover}
/>
)}
diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.test.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.test.tsx
new file mode 100644
index 0000000000000..273a0bcceb5f1
--- /dev/null
+++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.test.tsx
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiDataGridCellPopoverElementProps } from '@elastic/eui';
+import { render } from '@testing-library/react';
+import React from 'react';
+import { getCustomCellPopoverRenderer } from './get_render_cell_popover';
+
+const setCellPopoverPropsMocks = jest.fn();
+
+const DefaultCellPopover = () => {'DefaultCellPopover'}
;
+
+const defaultProps: EuiDataGridCellPopoverElementProps = {
+ rowIndex: 0,
+ colIndex: 0,
+ columnId: 'test_column',
+ setCellPopoverProps: setCellPopoverPropsMocks,
+ DefaultCellPopover,
+ cellActions: [],
+ children: {'children'}
,
+ cellContentsElement: ({'cellContentsElement'}
) as unknown as HTMLDivElement,
+};
+
+const renderTestComponent = () => {
+ const Renderer = getCustomCellPopoverRenderer();
+
+ render();
+};
+
+describe('getCustomCellPopoverRenderer', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should render DefaultCellPopover', () => {
+ renderTestComponent();
+
+ expect(setCellPopoverPropsMocks).toHaveBeenCalledWith({
+ panelClassName: 'unifiedDataTable__cellPopover',
+ });
+ });
+});
diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx
new file mode 100644
index 0000000000000..ea6e2a54a90de
--- /dev/null
+++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx
@@ -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 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 { EuiDataGridCellPopoverElementProps } from '@elastic/eui';
+import React, { memo, useEffect } from 'react';
+
+/*
+ *
+ * A custom cell popover render helps consumer of unified data table
+ * to get handle of unifiedDataTables's popover content and customize it.
+ *
+ * Default implementation is simply a pass through with just custom className.
+ * Consumers also have the ability to provide custom render functions
+ *
+ * */
+export const getCustomCellPopoverRenderer = () => {
+ return memo(function RenderCustomCellPopover(props: EuiDataGridCellPopoverElementProps) {
+ const { setCellPopoverProps, DefaultCellPopover } = props;
+
+ useEffect(() => {
+ setCellPopoverProps({
+ panelClassName: 'unifiedDataTable__cellPopover',
+ });
+ }, [setCellPopoverProps]);
+
+ return ;
+ });
+};
diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx
index 15bf0efb0c604..a56336fd53de7 100644
--- a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx
+++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React, { useContext, useEffect } from 'react';
+import React, { memo, useEffect, useContext } from 'react';
import { i18n } from '@kbn/i18n';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import {
@@ -47,7 +47,10 @@ export const getRenderCellValueFn = ({
externalCustomRenderers?: CustomCellRenderer;
isPlainRecord?: boolean;
}) => {
- return ({
+ /**
+ * memo is imperative here otherwise the cell will re-render on every hover on every cell
+ */
+ return memo(function UnifiedDataTableRenderCellValue({
rowIndex,
columnId,
isDetails,
@@ -55,7 +58,7 @@ export const getRenderCellValueFn = ({
colIndex,
isExpandable,
isExpanded,
- }: EuiDataGridCellValueElementProps) => {
+ }: EuiDataGridCellValueElementProps) {
const row = rows ? rows[rowIndex] : undefined;
const field = dataView.fields.getByName(columnId);
const ctx = useContext(UnifiedDataTableContext);
@@ -146,7 +149,7 @@ export const getRenderCellValueFn = ({
}}
/>
);
- };
+ });
};
/**
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
index e2433a7f1e127..6095429b2a942 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
@@ -38,7 +38,11 @@ import { DetailsPanel } from '../../../side_panel';
import { SecurityCellActionsTrigger } from '../../../../../app/actions/constants';
import { getFormattedFields } from '../../body/renderers/formatted_field_udt';
import ToolbarAdditionalControls from './toolbar_additional_controls';
-import { StyledTimelineUnifiedDataTable, StyledEuiProgress } from '../styles';
+import {
+ StyledTimelineUnifiedDataTable,
+ StyledEuiProgress,
+ UnifiedTimelineGlobalStyles,
+} from '../styles';
import { timelineActions } from '../../../../store';
import { transformTimelineItemToUnifiedRows } from '../utils';
import { TimelineEventDetailRow } from './timeline_event_detail_row';
@@ -411,6 +415,7 @@ export const TimelineDataTableComponent: React.FC = memo(
dataLoadingState === DataLoadingState.loadingMore) && (
)}
+
Date: Fri, 31 May 2024 19:03:32 +0100
Subject: [PATCH 29/46] [main] Sync bundled packages with Package Storage
(#184596)
Automated by
https://buildkite.com/elastic/package-storage-infra-kibana-discover-release-branches/builds/786
---
fleet_packages.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fleet_packages.json b/fleet_packages.json
index d0e5d49e1dd1c..2aa25a23b84e3 100644
--- a/fleet_packages.json
+++ b/fleet_packages.json
@@ -56,6 +56,6 @@
},
{
"name": "security_detection_engine",
- "version": "8.14.1"
+ "version": "8.14.2"
}
]
\ No newline at end of file
From 557633456ce89757b2c472bdcd0ccf2bfa601ce7 Mon Sep 17 00:00:00 2001
From: Ying Mao
Date: Fri, 31 May 2024 14:05:09 -0400
Subject: [PATCH 30/46] [Response Ops][Task Manager] Emitting metrics when
metrics are reset (#184592)
## Summary
This updates the task manager metrics aggregator to collect and emit
metrics when a `reset$` event is observed.
The `/api/task_manager/metrics` route subscribes to and saves the latest
task manager metrics and immediately returns the latest metrics when the
API is accessed. At a minimum, metrics are collected and emitted at
every polling interval (every 3 seconds). Usually emission is more
frequent than this because we emit metrics events every time a task run
completes.
Under normal circumstances, when the agent is configured to collect from
the API once every 10 seconds, this is what happens
```
00:00:00 metrics$.subscribe(({errors: 3}) => lastMetrics = metrics) - metrics emitted and saved
00:00:03 metrics$.subscribe(({errors: 4}) => lastMetrics = metrics) - metrics emitted and saved
00:00:05 API called with reset=true, return lastMetrics, metrics reset to 0
00:00:06 metrics$.subscribe(({errors: 1}) => lastMetrics = metrics) - metrics emitted and saved
00:00:09 metrics$.subscribe(({errors: 2}) => lastMetrics = metrics) - metrics emitted and saved
00:00:10 API called with reset=true, return lastMetrics, metrics reset to 0
```
We can see that the metrics are reset and then by the time the next
collection interval comes around, fresh metrics have been emitted.
We currently have an issue where the API is collected against twice in
quick succession. Most of the time, this leads to duplicate metrics
being collected.
```
00:00:00:00 metrics$.subscribe(({errors: 3}) => lastMetrics = metrics) - metrics emitted and saved
00:00:03:00 metrics$.subscribe(({errors: 4}) => lastMetrics = metrics) - metrics emitted and saved
00:00:05:00 API called with reset=true, return lastMetrics, metrics reset to 0
00:00:05:01 API called with reset=true, return lastMetrics, metrics reset to 0 - this is a duplicate
00:00:06:00 metrics$.subscribe(({errors: 1}) => lastMetrics = metrics) - metrics emitted and saved
00:00:09:00 metrics$.subscribe(({errors: 2}) => lastMetrics = metrics) - metrics emitted and saved
```
However sometimes, this leads to a race condition that leads to
different metrics being collected.
```
00:00:00:00 metrics$.subscribe(({errors: 3}) => lastMetrics = metrics) - metrics emitted and saved
00:00:03:00 metrics$.subscribe(({errors: 4}) => lastMetrics = metrics) - metrics emitted and saved
00:00:05:00 API called with reset=true, return lastMetrics, metrics reset to 0
00:00:05:01 metrics$.subscribe(({errors: 1}) => lastMetrics = metrics) - metrics emitted and saved
00:00:05:02 API called with reset=true, return lastMetrics, metrics reset to 0
00:00:06:00 metrics$.subscribe(({errors: 1}) => lastMetrics = metrics) - metrics emitted and saved
00:00:09:00 metrics$.subscribe(({errors: 2}) => lastMetrics = metrics) - metrics emitted and saved
```
With this PR, on every reset, we'll re-emit the metrics so so even in
the face of the duplicate collection, we won't be emitting duplicate
metrics. After this is deployed, we should not need to exclude
`kubernetes.container.name :"elastic-internal-init-config"` from the
dashboards
```
00:00:00:00 metrics$.subscribe(({errors: 3}) => lastMetrics = metrics) - metrics emitted and saved
00:00:03:00 metrics$.subscribe(({errors: 4}) => lastMetrics = metrics) - metrics emitted and saved
00:00:05:00 API called with reset=true, return lastMetrics, metrics reset to 0
00:00:05:00 metrics$.subscribe(({errors: 0}) => lastMetrics = metrics) - metrics emitted and saved
00:00:05:01 API called with reset=true, return lastMetrics, metrics reset to 0
00:00:05:01 metrics$.subscribe(({errors: 0}) => lastMetrics = metrics) - metrics emitted and saved
00:00:06:00 metrics$.subscribe(({errors: 1}) => lastMetrics = metrics) - metrics emitted and saved
00:00:09:00 metrics$.subscribe(({errors: 2}) => lastMetrics = metrics) - metrics emitted and saved
```
---
.../server/metrics/create_aggregator.test.ts | 316 +++++++++++-------
.../server/metrics/create_aggregator.ts | 24 +-
.../test_suites/task_manager/metrics_route.ts | 13 +-
3 files changed, 223 insertions(+), 130 deletions(-)
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 309617a8e4cc3..1db164e38d992 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
@@ -7,7 +7,7 @@
import sinon from 'sinon';
import { Subject } from 'rxjs';
-import { take, bufferCount, skip } from 'rxjs';
+import { take, bufferCount } from 'rxjs';
import { loggingSystemMock } from '@kbn/core/server/mocks';
import {
isTaskManagerMetricEvent,
@@ -109,13 +109,7 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskClaimAggregator
- .pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events.length),
- bufferCount(events.length)
- )
+ .pipe(take(events.length), bufferCount(events.length))
.subscribe((metrics: Array>) => {
expect(metrics[0]).toEqual({
key: 'task_claim',
@@ -268,11 +262,8 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskClaimAggregator
.pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events1.length + events2.length),
- bufferCount(events1.length + events2.length)
+ take(events1.length + events2.length + 1),
+ bufferCount(events1.length + events2.length + 1)
)
.subscribe((metrics: Array>) => {
expect(metrics[0]).toEqual({
@@ -337,6 +328,16 @@ describe('createAggregator', () => {
});
// reset event should have been received here
expect(metrics[6]).toEqual({
+ key: 'task_claim',
+ value: {
+ success: 0,
+ total: 0,
+ total_errors: 0,
+ duration: { counts: [], values: [] },
+ duration_values: [],
+ },
+ });
+ expect(metrics[7]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -346,7 +347,7 @@ describe('createAggregator', () => {
duration_values: [10],
},
});
- expect(metrics[7]).toEqual({
+ expect(metrics[8]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -356,7 +357,7 @@ describe('createAggregator', () => {
duration_values: [10],
},
});
- expect(metrics[8]).toEqual({
+ expect(metrics[9]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -366,7 +367,7 @@ describe('createAggregator', () => {
duration_values: [10],
},
});
- expect(metrics[9]).toEqual({
+ expect(metrics[10]).toEqual({
key: 'task_claim',
value: {
success: 2,
@@ -376,7 +377,7 @@ describe('createAggregator', () => {
duration_values: [10, 10],
},
});
- expect(metrics[10]).toEqual({
+ expect(metrics[11]).toEqual({
key: 'task_claim',
value: {
success: 3,
@@ -435,11 +436,8 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskClaimAggregator
.pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events1.length + events2.length),
- bufferCount(events1.length + events2.length)
+ take(events1.length + events2.length + 1),
+ bufferCount(events1.length + events2.length + 1)
)
.subscribe((metrics: Array>) => {
expect(metrics[0]).toEqual({
@@ -504,6 +502,16 @@ describe('createAggregator', () => {
});
// reset interval should have fired here
expect(metrics[6]).toEqual({
+ key: 'task_claim',
+ value: {
+ success: 0,
+ total: 0,
+ total_errors: 0,
+ duration: { counts: [], values: [] },
+ duration_values: [],
+ },
+ });
+ expect(metrics[7]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -513,7 +521,7 @@ describe('createAggregator', () => {
duration_values: [10],
},
});
- expect(metrics[7]).toEqual({
+ expect(metrics[8]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -523,7 +531,7 @@ describe('createAggregator', () => {
duration_values: [10],
},
});
- expect(metrics[8]).toEqual({
+ expect(metrics[9]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -533,7 +541,7 @@ describe('createAggregator', () => {
duration_values: [10],
},
});
- expect(metrics[9]).toEqual({
+ expect(metrics[10]).toEqual({
key: 'task_claim',
value: {
success: 2,
@@ -543,7 +551,7 @@ describe('createAggregator', () => {
duration_values: [10, 10],
},
});
- expect(metrics[10]).toEqual({
+ expect(metrics[11]).toEqual({
key: 'task_claim',
value: {
success: 3,
@@ -605,14 +613,22 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskClaimAggregator
.pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events1.length + events2.length + 1),
- bufferCount(events1.length + events2.length + 1)
+ take(events1.length + events2.length + 3),
+ bufferCount(events1.length + events2.length + 3)
)
.subscribe((metrics: Array>) => {
+ // reset event
expect(metrics[0]).toEqual({
+ key: 'task_claim',
+ value: {
+ success: 0,
+ total: 0,
+ total_errors: 0,
+ duration: { counts: [], values: [] },
+ duration_values: [],
+ },
+ });
+ expect(metrics[1]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -622,7 +638,7 @@ describe('createAggregator', () => {
duration_values: [10],
},
});
- expect(metrics[1]).toEqual({
+ expect(metrics[2]).toEqual({
key: 'task_claim',
value: {
success: 2,
@@ -632,7 +648,7 @@ describe('createAggregator', () => {
duration_values: [10, 10],
},
});
- expect(metrics[2]).toEqual({
+ expect(metrics[3]).toEqual({
key: 'task_claim',
value: {
success: 3,
@@ -642,7 +658,7 @@ describe('createAggregator', () => {
duration_values: [10, 10, 10],
},
});
- expect(metrics[3]).toEqual({
+ expect(metrics[4]).toEqual({
key: 'task_claim',
value: {
success: 4,
@@ -652,7 +668,7 @@ describe('createAggregator', () => {
duration_values: [10, 10, 10, 10],
},
});
- expect(metrics[4]).toEqual({
+ expect(metrics[5]).toEqual({
key: 'task_claim',
value: {
success: 4,
@@ -662,7 +678,7 @@ describe('createAggregator', () => {
duration_values: [10, 10, 10, 10],
},
});
- expect(metrics[5]).toEqual({
+ expect(metrics[6]).toEqual({
key: 'task_claim',
value: {
success: 5,
@@ -673,7 +689,7 @@ describe('createAggregator', () => {
},
});
// reset interval fired here but stats should not clear
- expect(metrics[6]).toEqual({
+ expect(metrics[7]).toEqual({
key: 'task_claim',
value: {
success: 6,
@@ -683,7 +699,7 @@ describe('createAggregator', () => {
duration_values: [10, 10, 10, 10, 10, 10],
},
});
- expect(metrics[7]).toEqual({
+ expect(metrics[8]).toEqual({
key: 'task_claim',
value: {
success: 6,
@@ -693,7 +709,7 @@ describe('createAggregator', () => {
duration_values: [10, 10, 10, 10, 10, 10],
},
});
- expect(metrics[8]).toEqual({
+ expect(metrics[9]).toEqual({
key: 'task_claim',
value: {
success: 6,
@@ -703,7 +719,7 @@ describe('createAggregator', () => {
duration_values: [10, 10, 10, 10, 10, 10],
},
});
- expect(metrics[9]).toEqual({
+ expect(metrics[10]).toEqual({
key: 'task_claim',
value: {
success: 7,
@@ -713,7 +729,7 @@ describe('createAggregator', () => {
duration_values: [10, 10, 10, 10, 10, 10, 10],
},
});
- expect(metrics[10]).toEqual({
+ expect(metrics[11]).toEqual({
key: 'task_claim',
value: {
success: 8,
@@ -724,7 +740,17 @@ describe('createAggregator', () => {
},
});
// reset interval fired here and stats should have cleared
- expect(metrics[11]).toEqual({
+ expect(metrics[12]).toEqual({
+ key: 'task_claim',
+ value: {
+ success: 0,
+ total: 0,
+ total_errors: 0,
+ duration: { counts: [], values: [] },
+ duration_values: [],
+ },
+ });
+ expect(metrics[13]).toEqual({
key: 'task_claim',
value: {
success: 1,
@@ -795,13 +821,7 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskRunAggregator
- .pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(taskRunEvents.length),
- bufferCount(taskRunEvents.length)
- )
+ .pipe(take(taskRunEvents.length), bufferCount(taskRunEvents.length))
.subscribe((metrics: Array>) => {
expect(metrics[0]).toEqual({
key: 'task_run',
@@ -1824,11 +1844,8 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskRunAggregator
.pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(taskRunEvents1.length + taskRunEvents2.length),
- bufferCount(taskRunEvents1.length + taskRunEvents2.length)
+ take(taskRunEvents1.length + taskRunEvents2.length + 1),
+ bufferCount(taskRunEvents1.length + taskRunEvents2.length + 1)
)
.subscribe((metrics: Array>) => {
expect(metrics[0]).toEqual({
@@ -2225,6 +2242,55 @@ describe('createAggregator', () => {
});
// reset event should have been received here
expect(metrics[10]).toEqual({
+ key: 'task_run',
+ value: {
+ overall: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ delay: { counts: [], values: [] },
+ delay_values: [],
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ by_type: {
+ alerting: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ 'alerting:example': {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ report: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ telemetry: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ },
+ },
+ });
+ expect(metrics[11]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2273,7 +2339,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[11]).toEqual({
+ expect(metrics[12]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2322,7 +2388,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[12]).toEqual({
+ expect(metrics[13]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2371,7 +2437,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[13]).toEqual({
+ expect(metrics[14]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2420,7 +2486,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[14]).toEqual({
+ expect(metrics[15]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2469,7 +2535,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[15]).toEqual({
+ expect(metrics[16]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2518,7 +2584,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[16]).toEqual({
+ expect(metrics[17]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2567,7 +2633,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[17]).toEqual({
+ expect(metrics[18]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2616,7 +2682,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[18]).toEqual({
+ expect(metrics[19]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2665,7 +2731,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[19]).toEqual({
+ expect(metrics[20]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -2789,11 +2855,8 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskRunAggregator
.pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(taskRunEvents1.length + taskRunEvents2.length),
- bufferCount(taskRunEvents1.length + taskRunEvents2.length)
+ take(taskRunEvents1.length + taskRunEvents2.length + 1),
+ bufferCount(taskRunEvents1.length + taskRunEvents2.length + 1)
)
.subscribe((metrics: Array>) => {
expect(metrics[0]).toEqual({
@@ -3190,6 +3253,55 @@ describe('createAggregator', () => {
});
// reset event should have been received here
expect(metrics[10]).toEqual({
+ key: 'task_run',
+ value: {
+ overall: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ delay: { counts: [], values: [] },
+ delay_values: [],
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ by_type: {
+ alerting: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ 'alerting:example': {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ report: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ telemetry: {
+ success: 0,
+ not_timed_out: 0,
+ total: 0,
+ framework_errors: 0,
+ user_errors: 0,
+ total_errors: 0,
+ },
+ },
+ },
+ });
+ expect(metrics[11]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3238,7 +3350,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[11]).toEqual({
+ expect(metrics[12]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3287,7 +3399,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[12]).toEqual({
+ expect(metrics[13]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3336,7 +3448,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[13]).toEqual({
+ expect(metrics[14]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3385,7 +3497,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[14]).toEqual({
+ expect(metrics[15]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3434,7 +3546,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[15]).toEqual({
+ expect(metrics[16]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3483,7 +3595,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[16]).toEqual({
+ expect(metrics[17]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3532,7 +3644,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[17]).toEqual({
+ expect(metrics[18]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3581,7 +3693,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[18]).toEqual({
+ expect(metrics[19]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3630,7 +3742,7 @@ describe('createAggregator', () => {
},
},
});
- expect(metrics[19]).toEqual({
+ expect(metrics[20]).toEqual({
key: 'task_run',
value: {
overall: {
@@ -3771,13 +3883,7 @@ describe('createAggregator', () => {
return new Promise((resolve) => {
taskOverdueAggregator
- .pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events.length),
- bufferCount(events.length)
- )
+ .pipe(take(events.length), bufferCount(events.length))
.subscribe((metrics: Array>) => {
expect(metrics[0]).toEqual({
key: 'task_overdue',
@@ -3933,17 +4039,9 @@ describe('createAggregator', () => {
});
return new Promise((resolve) => {
- aggregator
- .pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events.length),
- bufferCount(events.length)
- )
- .subscribe(() => {
- resolve();
- });
+ aggregator.pipe(take(events.length), bufferCount(events.length)).subscribe(() => {
+ resolve();
+ });
for (const event of events) {
events$.next(event);
@@ -3984,17 +4082,9 @@ describe('createAggregator', () => {
});
return new Promise((resolve) => {
- aggregator
- .pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events.length),
- bufferCount(events.length)
- )
- .subscribe(() => {
- resolve();
- });
+ aggregator.pipe(take(events.length), bufferCount(events.length)).subscribe(() => {
+ resolve();
+ });
for (const event of events) {
events$.next(event);
@@ -4040,17 +4130,9 @@ describe('createAggregator', () => {
});
return new Promise((resolve) => {
- aggregator
- .pipe(
- // skip initial metric which is just initialized data which
- // ensures we don't stall on combineLatest
- skip(1),
- take(events.length),
- bufferCount(events.length)
- )
- .subscribe(() => {
- resolve();
- });
+ aggregator.pipe(take(events.length + 1), bufferCount(events.length + 1)).subscribe(() => {
+ resolve();
+ });
for (const event of events) {
events$.next(event);
diff --git a/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts b/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts
index a06278dd12ef7..3b2bb8726a5ec 100644
--- a/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts
+++ b/x-pack/plugins/task_manager/server/metrics/create_aggregator.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { combineLatest, filter, interval, map, merge, Observable, startWith } from 'rxjs';
+import { filter, interval, map, merge, Observable } from 'rxjs';
import { JsonValue } from '@kbn/utility-types';
import { Logger } from '@kbn/core/server';
import { AggregatedStat, AggregatedStatProvider } from '../lib/runtime_statistics_aggregator';
@@ -32,11 +32,12 @@ export function createAggregator({
eventFilter,
metricsAggregator,
}: CreateMetricsAggregatorOpts): AggregatedStatProvider {
+ let taskResetEvent$: Observable | undefined;
if (reset$) {
let lastResetTime: Date = new Date();
// Resets the aggregators either when the reset interval has passed or
// a reset$ event is received
- merge(
+ taskResetEvent$ = merge(
interval(config.metrics_reset_interval).pipe(
map(() => {
if (intervalHasPassedSince(lastResetTime, config.metrics_reset_interval)) {
@@ -62,11 +63,13 @@ export function createAggregator({
return true;
})
)
- ).subscribe((shouldReset: boolean) => {
- if (shouldReset) {
+ ).pipe(
+ filter((shouldReset: boolean) => shouldReset),
+ map(() => {
metricsAggregator.reset();
- }
- });
+ return metricsAggregator.collect();
+ })
+ );
}
const taskEvents$: Observable = events$.pipe(
@@ -77,8 +80,13 @@ export function createAggregator({
})
);
- return combineLatest([taskEvents$.pipe(startWith(metricsAggregator.initialMetric()))]).pipe(
- map(([value]: [T]) => {
+ const observablesToMerge: Array> = [taskEvents$];
+ if (taskResetEvent$) {
+ observablesToMerge.push(taskResetEvent$);
+ }
+
+ return merge(...observablesToMerge).pipe(
+ map((value: T) => {
return {
key,
value,
diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts
index fb8ee402fcc88..8d79b4250de60 100644
--- a/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts
+++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts
@@ -133,8 +133,8 @@ export default function ({ getService }: FtrProviderContext) {
expect(metrics?.task_claim).not.to.be(null);
expect(metrics?.task_claim?.value).not.to.be(null);
- expect(metrics?.task_claim?.value.success).to.equal(1);
- expect(metrics?.task_claim?.value.total).to.equal(1);
+ expect(metrics?.task_claim?.value.success).to.equal(0);
+ expect(metrics?.task_claim?.value.total).to.equal(0);
previousTaskClaimTimestamp = metrics?.task_claim?.timestamp!;
@@ -264,7 +264,10 @@ export default function ({ getService }: FtrProviderContext) {
.expect(200);
const metrics = (
- await getMetrics(true, (m) => m?.metrics?.task_run?.value.overall.framework_errors! === 1)
+ await getMetrics(
+ false,
+ (m) => m?.metrics?.task_run?.value.overall.framework_errors! === 1
+ )
).metrics;
const total = metrics?.task_run?.value.overall.total || 0;
@@ -302,13 +305,13 @@ export default function ({ getService }: FtrProviderContext) {
.expect(200);
const metrics = (
- await getMetrics(true, (m) => m?.metrics?.task_run?.value.overall.user_errors! === 1)
+ await getMetrics(false, (m) => m?.metrics?.task_run?.value.overall.user_errors! === 1)
).metrics;
const total = metrics?.task_run?.value.overall.total || 0;
const success = metrics?.task_run?.value.overall.success || 0;
- expect(total - success).to.be(1);
+ expect(total - success).to.be(2);
});
});
From 310f4ff79cbe5d2ec7e699d9ffb3aefdc51da9ec Mon Sep 17 00:00:00 2001
From: DeDe Morton
Date: Fri, 31 May 2024 11:26:12 -0700
Subject: [PATCH 31/46] [DOCS] Obs AI Assistant connector (#183792)
## Summary
Adds reference documentation about the Obs AI Assistant connector
(requested in #181282)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
docs/management/action-types.asciidoc | 6 +-
.../action-types/obs-ai-assistant.asciidoc | 67 ++++++++++++++++++
.../images/obs-ai-assistant-action.png | Bin 0 -> 55511 bytes
docs/management/connectors/index.asciidoc | 1 +
4 files changed, 73 insertions(+), 1 deletion(-)
create mode 100644 docs/management/connectors/action-types/obs-ai-assistant.asciidoc
create mode 100644 docs/management/connectors/images/obs-ai-assistant-action.png
diff --git a/docs/management/action-types.asciidoc b/docs/management/action-types.asciidoc
index 7729b85699eb6..0d8f43925a1fc 100644
--- a/docs/management/action-types.asciidoc
+++ b/docs/management/action-types.asciidoc
@@ -40,6 +40,10 @@ a| <>
| Send a message to a Microsoft Teams channel.
+a| <>
+
+| Add AI-driven insights and custom actions to your workflow.
+
a| <>
| Send a request to OpenAI.
@@ -144,7 +148,7 @@ image::images/connector-filter-by-type.png[Filtering the connector list by types
// NOTE: This is an autogenerated screenshot. Do not edit it directly.
You can delete individual connectors using the trash icon. Alternatively, select
-multiple connectors and delete them in bulk using the *Delete* button.
+multiple connectors and delete them in bulk using the *Delete* button.
[role="screenshot"]
image::images/connector-delete.png[Deleting connectors individually or in bulk]
diff --git a/docs/management/connectors/action-types/obs-ai-assistant.asciidoc b/docs/management/connectors/action-types/obs-ai-assistant.asciidoc
new file mode 100644
index 0000000000000..500215b2fab45
--- /dev/null
+++ b/docs/management/connectors/action-types/obs-ai-assistant.asciidoc
@@ -0,0 +1,67 @@
+[[obs-ai-assistant-action-type]]
+== Observability AI Assistant connector and action
+++++
+Observability AI Assistant
+++++
+:frontmatter-description: Add a rule action that adds AI-driven insights and custom actions to your workflow.
+:frontmatter-tags-products: [kibana]
+:frontmatter-tags-content-type: [how-to]
+:frontmatter-tags-user-goals: [configure]
+
+preview::[]
+
+The Observability AI Assistant connector adds AI-driven insights and custom actions to your workflow.
+
+To learn how to interact with the assistant through this connector, refer to the {observability-guide}/obs-ai-assistant.html[Observability AI Assistant] documentation.
+
+[float]
+[[define-obs-ai-assistant-ui]]
+=== Create connectors in {kib}
+
+To use this connector, you must have been granted access to use the Observability AI Assistant feature.
+You cannot manage this connector in *{stack-manage-app} > {connectors-ui}* or by using APIs.
+You also cannot create an Observability AI Assistant <>.
+It is available only when you're creating a rule in {kib}.
+For example:
+
+[role="screenshot"]
+image::management/connectors/images/obs-ai-assistant-action.png[Add an Observability AI Assistant action while creating a rule in the Observability UI]
+
+NOTE: You can have only one Observability AI Assistant action in each rule.
+
+[float]
+[[obs-ai-assistant-connector-configuration]]
+==== Connector configuration
+
+Observability AI Assistant connectors have the following configuration properties:
+
+Connector::
+The name of the connector to use to send requests to your AI provider.
+For more information, refer to <> or <>.
+
+Message::
+A message containing the prompt to send to the Observability AI Assistant.
+The message can specify a set of tasks for the assistant to perform, such as creating a graph or report,
+and it can call an available connector to send messages to an external system, such as Slack.
+
+NOTE: Currently you can only send messages to a Slack webhook.
+Support for additional connectors will be added in the future.
+
+For example, you can create a rule that sends the following prompt to the AI Assistant when an error count threshold is breached:
+
+[source,text]
+-----
+High error count alert has triggered. Execute the following steps:
+ - create a graph of the error count for the service impacted by the alert
+ for the last 24h
+ - to help troubleshoot, recall past occurrences of this alert, plus any
+ other active alerts. Generate a report with all the found information
+ and send it to the Slack connector as a single message. Also include
+ the link to this conversation in the report.
+-----
+
+[float]
+[[obs-ai-assistant-action-configuration]]
+=== Test connectors
+
+You cannot test or edit these connectors in {kib} or by using APIs.
diff --git a/docs/management/connectors/images/obs-ai-assistant-action.png b/docs/management/connectors/images/obs-ai-assistant-action.png
new file mode 100644
index 0000000000000000000000000000000000000000..f452a8b0ae64f99d2c667d3bebef875fef801000
GIT binary patch
literal 55511
zcmdSBRajhG6D^7d2o^jzNpN>}cXtoLJvhPL0>L4;H16&iAhMHcXw)BeOPkxpF&-zpY#p0D1M4ynxA>Lv|T)8jg5`3
z^&`F~e??9P7wnIL0V67mDDDhnKh^FxE%2W;YM4C8_kh3GwBy0?XB<;V_I!9Zs-qjP
zh=>UNzpFh6;og4>7rEwn>y|`Pm-iMqmW`X@^Zzymt^so*2@6fh#=eE8Mxa95`F?-4
zBR6L_LQVY0$@kV3>7PviF}iV~xthLY@#;d?qDVa|%f@Qj*7pY4b=ccjm62mIJ`0N&
z5dJPFiNHp#VsDSTqv8-f7m|uYQgK8rK^00^a~f{giDHI5z4Kl5SwajLhfM!;!aGQH
zvZx5t95Ws^2{qq5HYo@qT*IR0G>WXGjN-21wsy;yh#c~dN)Q98G5pW4AZPF`U8)|V
zk?7cDAe3C;y54-896Tn&FVy@dJq%WVuXdM!%2#1oRL+N_Xblk6`3AYQ>_gdFhEOpx
ze_0ja@9%}BegkOwe@YWhB!gli;&Mk1B*}Lr_~B?9rtF1b^6uv=*8j^Y3^4qo{m5vL
z*LBn&sTY<)B(|mo8Oa>4bL6rV;{GE--0IrtAXYZTviR%nK8jy!;9?@?YZsdL^U9
z@F&5t?e=_FCazym_Hv>6f{6Sj{MkTcaCvW_?Y`I8wyAp6?@Q6~d9AS4*uH$eg(z7q
z)}&W7$k0?95b{yt5>4jrU&jG%WBl1)CwnRqeY#{xk5`zgR?|QDKSm`h#b7>U_xh1q
zTd5$j7p)nXCp%b>`K$
zT%)|dDrWTd>rP0ryx*#MpUL`qidfV8_o|;L(ssY*@a3oAx4JHK$&C4lBo#jwWw99$
zrKtc7hO5Gn2%P3qb^0bUYP=zF`%-jF-Ef}gVVVm^AxES|!?)l~GAYN2@GbTbr~lK%
zBgcCAj4`p14i~+E?kP0x`R!fc(DAKjo*Y#~mQa3*8A0&gEs+)kxcMvp+RvuovxZMU2e{->sv^Zv
zVax>v+DYD$k&z*0jd_@{`Ch-fJKvq4F%0~sl5gR#DBJWvIAnK48@cJMl7y}Os}4hU
z;`SoFc)hAirvjvu{ad|L|8Bk7H2Maxc2Ha1SjdYZvZjBVRR=#iumQ|XztX4H7X6-
zB$sN<>TcS+E@s?*QMh(_U%Orx{9>ul4f3!&nctMn1
zi=^WMtrsd4R+=3AGi!CiAJ-~sj@&)~_o|IUlDvpEnA
zCgifmw|Yg&=VqJVx>RRDMTs+GjYC#^vg_L9YV7rxV*8mu5a0mDa##LOfZMnk3iRVwHOL6GK{^+IN8J
z6=^K1-GoEYhzZ`LYmP$3R;SOf-_#@!D$9;%3n^K?_;3r^C;!-c0B^Q6TcSqmeY(|8
zPOmeRK!x7p26#WM!NF`viS1IYwa?CQk~NZ$s)6sr?S)2ag`dyeC8|oLqEUq|M-qG_
zD%@~(vO+p%$sB`TW3ky>sfMe1b22yQ!+lVN)F!PkD~|-!@c2?qKDAByrMkzNOR3jU
zpe`kiw)PHs*_LS~q(jYXb)4j#m*dE2_b;!hypW-pnbZwjS_64sXR7I7jTmA+k!>pZ
zJo7Oj(un^ZG3T
zhC4f_n)#-1ls;5=CK8v1>+5T33WP`$Uu9deWmOo8JkRrA-jAPDrwe4PE)M4EUG`OF
z(^$>I3wykBM!Ki7a`>rLV#Mh*?N%B=qiJkd^xDVE4Qdze2eU)p(V|A8U$f0#{v1k}
z`v|D_m3hq*xDQ<;?dxZi)3PLw)2-JcsmSH>)r
zheuXtUaVZwqU`s+4V^aSgbpdnDUKgJ?mcDZXpo8>=bX9Pv*#PvEX-`XR9HodA)-L84S{L3d9S_^&2GT_-roq%X|$M
zDh+l$2K%xtXD$x8JV+hXcOLE>z*rXIjW}jHADs&CPr6Pd4cac;D6N{OoqB&;+cF$6!KBy_vOF7HTHBKO`^k_D^>}r{V;@?XN4a)ZDwqx
zV0?qm@*d7a`{i&HF3Jm?1i&^W8*d~c@u!qKmAe|P$zb-CDMLRCK0wrl)47;>9FQWv
zr6bY{blso5Y_OaX#ZfC!1!44mCgO=VX6dQMERap2wQiQ9>gBO9SNuuhJ+>mJQwH
zE$G9XuD4RVK*MP;+0Lf0f#AT9l4@_|U=#CuC^N9ozc2*j^NNi8T*II1FMnCvv)IClFvtO4%TYq!10SHmexRcaEU
z6cuXlx^fT*3%rm1xNh_uv%Jg`nU9xl`!(tE?SX&*g&cN1ud9vfJbhZluNoYWU!y#3
ziuZBZYpj#SLZK=d+W<%%_jj4j7*ah0;_(vN9mKlv-Uldt`h2s_f!&ipXMikH~K`
zgoU>u8W~ow!`}ideMQB!AMbBw(GTYfL>_p9v+3Z`$^ZwRqhj{)jYaD
zr7m}MSZ&rZlSyGjWiYCVJeWuA1nbcazIL0RUfh1q(9zJ!(3df`Ys))ww!XuOjjXL&
zZwW6($B`TfYyb7SCorc1@%!DJmQf9+p(I|u{n|jN6$m(ShE>@t#)$$kKPYWnN2POk
zs|sFkz`otTI9!{{=7fuBR5J$U=TB#Q3}1l@xsNDJeDygR-RW{i#$(NiyCN
z4S^71xrkS|0z6m<{F==?daVF>90?Aqzlt~oahQ*N<+ulP$@R~bX-!C*+Vsaa=mhjA
z<6+C81c>A$_VBjbF0q>K)dJTwMka%$g@w^|tmI0f#K5z)x}suiUXF#r5L
zgPH$O`8_Xq=EM1~&-;F3>B|i7hiCWMT@U05c-~jf$j1MGu@mnso*9sgJ~nO-t_B?W
zzAkehEmgx-Ae?w53gPLpEEh~hiXD|D&ot=Lg7vre2T)E6Vkc|1*X+O0p9b}SH
zrRyyQ#NLgMjo_Ir!Gq}=zKg6N7E`K8hoIY2thq2r&o=iHP(VZn!pjV$MUt?hwtOh=J#589x8$kU%#Yif%S}uk4@R|5-pe>@}*+;N6d|89`4ZN
z60H|*qDwU@5xgFj9Y{k<0<4&Y~atx4f)RA6Uqj1^kCq42Y=ihuPA4mpWdr}-yeDE
z31I?)sRE6W!=cmND7@90x}*^
z2m}1E1Q3mo>V}OC0{Y?SaT(Uu*2q)$96Rps&nJ?mVb{rihHNELDUlG{Ia>`aXJD{(
zO)}c?1inGGaQogH(zAdl7nQ`*Rd;wTn9}yCc%`szi6RpgX4zs;g!L^!pE3A>M&GgZ
zmAPR};RJr4-mlr%A477Q%6l+!f%td2GXecHp^PG=iw>
z@rB-x36JRANVejMrRUK#l3VlqX=qb%rbB#;nBiapUOy~6cAcVHGvd6L*eiSD00Dkg
z!Y&m<#5TP_$!$5wjI(fd_)B((Mw_{-mB)VZx=Oa_slupBCS-RkJ*?U_Cemdar`A;yRJ!w!p+rwRPD-GjuZQ&L(l
ziS(bYx}IJO9oI+E8~_MUZBA^8M!on8!9YN7P&!-a$6mVqrT4at{)@ClfM;4aBXHSh
zy8;mqgiTWLv>8UK&UszceU>QX=L<>{Vd+D+`}QS;iSva4KFO#eHCTiA{-!O(}i;Yiw?Msw)DEDwNtCkUEoh
zR{Axo7keb1yL=;l3AAJ7T|-;360Y2TQ4A>mxrlXj+oRNYIZcKZ3GJ=+mE*40Tva=f
zEQQfPmCGWt`pr7?rz>PmwmFlR;}br2=cuPlr79iBdeK|dMeBJIFI5jU8Pt=33};eE
zP77a5ZDpyo&h-$jk@>>ZFD;OEY&V-LN4J4+`de2aSb627mA2E;yf6amk-f17W@JjY
z(an42)@DaNVl%tpkW;nd>)aydD>)urtuoyRCf3&|BPu1T6vg%(Odin>%sVDKJ@kIj
zM6&xcMQS1-S)8IOy+%{4n>W^T2XxM?VTVg~e&4yIg+O2&2G#JS#0**`lSclYJOl{^
zbU9)HFMS%dBs#5XJx2L0AcNT5u8$z_qEow7+wp5K8{yQf?)B}{8_Xco6xCW~v6>eW0FU8||$4MF)S4_dYOzrR~pVK)~n$A6v~Yyv{yYc$eGA
z3@M!`d}Yrww*}2`325))GTuwknDL_Q12IG%q$3jaVJKdQMou|zB>5qcflOk5_2#Ab
z`45~t@1(_XAtrnD&X1o2>MKjXKWe#a4o%q7pg
zAqQpEjeJ4`rL22i&;TLtBTBd_q8(2+CuG{HCEWmyiB`2RZ
zSgiPL->@I_M?q;A%1@+nj$CY{zk27Ny*R2`U`c*XIcJ)U!S7?#?5Ls>gN%DEPD4IZ
zI98y_9}GiUAer5f_2_x_(<+G+523D)iT-tSPr8D?MwvRY3LdAU+G>p{7x5-}syFG*
z&dUjsq(adUR_ln5r|)9Zx97?hJR&R5Gj|(B>^o|Qi%}m_cwJ@1HbwC1nQqSg#Hp5s
zc=F)53Wt+u!B^ACH9KXnJ*zP$T+&W2I(e3#3gy!_s0Pu6M)S(a=`<^m>ZW1K<_ugPNW05)igcRy_ccj?<{mswr0qxV(Cy)X?wkCck#IQPQ?F(620sS@p7VArF)_Z#RM=oEAwY$
z@=8HmgMAUpN78mNFAsTzU}sDpDTxHdX)#aX;<1q%gY!SgtGH{9-ee4&YbKJge=ENx
z`t_RYOVEqh(oH7fA{c{vblj|_3UH2Gv!x03=Ip+4LS=(})-IK)bYb-CYG$~|03GIJ
zw40NzGTD8VPJHjMRDQG8TMF4k{|74qx-OD}Ht_I1M%_R;lD(sDyT@LiDswt=a)QFq
zmzN-mhfu#caI9}_Ux+>K1b;ecM<6TO*(phqkr=Sk-LDM4{^Z$$f|R
zju=&|Q-T*|Fabn{qAAF*eD!@PJ!{;eC3qSO;9h@MZ(+T*XRMZOmpcW65kcVgk<0c8
z*X`7AH4&!j-#FlU4-(6#@jM>DJMv{~2WvCKsPuxQei^$Jbo@}PkGEzP&CTyn?XTP1
zPE?faZz*%>dQtGNb3Tk`kk5X;VddlA9w?URs-rY3-5`8Jx;X6GD_UwV>`zP58%~Um9+@Sy$l3k;57d=ummh3d5od
zQNR5as&Dw`i8Ov?MTeYrooGouCu^|xZu47`&6ijKc~Ep?trvteQkS-gZ>2bUU2bC+
zTbd=u5M1Ij^H@I7@(qe8o?^te5M3v~lTT!xRgP4Nvy#wL^d;p~4SAm`Ibzl+BlvI-y!HfwKXzTKr&lX9xG%yZL%ocU&l!G60dyXu8=?Q+vm>WirTX}%_
z2w_Ami@)Vq6FTP0W7i}0@KXC&(>mRYqe6u)=0t%{Atuy_ecG;vvl}MKjpXrLAyJOE
zAyqhu#O*eSVqfs%M=wn@FFxJ>WEsnBlkNz96&auJV_VssW-40bclJ%Isx%jW^joQJ
z!y7tdvuMX_vk)oB7mWL*QB86wvz{~_llDcIv0Z4NFO)1TUX&^Mm(url3S|Bkwwv1D
zSRzc-{`fO5yg<3yYD3n!CWAnw$c}f(Cz2oTf-FTp@63adGs0Jz?m{JNP7u~E^-4b=
z?MNfPoCtsoyLrG#+GC@u)89zUcxUyVLk&T#hQ&50ou&}Ov~b~L)+4IaMpc)+qSI2B
z`P@N6>5rQ;pMvC6T(lQvqm3owXdUn#x-9nf9wP4(2C{iT5X%uH`=0=D#hOT{?*E$q
z%PAJ6+J$w}{;LVvo5QLS+R^1M!wx^yNGAJneB8cC9aOhSv{=h9OZm)B*Skj5vwBmD
zmReBKbe;3AA&04gUSath?0SURh$ih*UV%&y4y!XSw=g;(XQWSF3|%&!b4J4jK+csK
zb6@Bpn^WgaEGj8`kyHn=+poTB9m!tzhh+{;oy64+j(;a}G%Lut08N*dZwj-&$t~pY
z_}&f9=)2Ewen#<2?CVsYxAUcfy}I>LVJ~uv!=zZvM{{aTimMUtsC8~>N~8&&jCVYT
z`U+F3e&n;$U`6&1B2Xc@F+gj3#^3wbQO8J8=x<_rswm*?a7>V~J50=Q_CE3X5-EF^yL&DYglt$#zL!VE?el)VUw_
z_NkyS!!#U$OK$Y5^-5!d?AHFiNQZ}|4W!uV9(QiTs$V*2e*kP2kkh%2@F3e48mr2d
z$=pFK_!_mD!N}(VB)dHtjcUhJbnYn`ylYh>WO;2L=53}!?e>=>B
z>yQ&F{F;z^1JCrDQ4)m&`?yBd>HbU6vbpaC8qsy1_b4(#
zp_Zv+xaJC7#xa=GP`p$rn)*M-ZbLdM0IO!>Cc*X>@}
z6Zi0P>Z;Vq9xpJzrTN$j+Gohbe%$nq*_;#_l>cdU){itlIG)*=PkIZ{sA3c*Kza4v
zD6gUwtwX#W)SPxnYCff5V&*gyzjIKrO{YcAIb1zzVaMmI&-8JdDwdAxw_4tH&0NZY
z*l!=3mUr@q?L5%>x0`)IJ}
z)up{h$uhx&I~Q6r4Kx!L_P_0y`?8q9s9S#G?GZbiy=>9Mlv=w+ZfBl^aI)=WKHQp~
zFf-3U7rI2+e%wK=wA<4;)hD_fpSumgtmEIHetGE6Aj0F366uK>Q&De%8a9XY$TJjA
zslrxxwBrZf$o=|==9d?a0eNXOIUrBSz--ZI{Ue4L-IT|hC2Rb=GG&p=X?vi7hv?_1
zAm>$28pNXtD|B+ySKj?(O^q!KJg@f&m(|&zao#Dz^vUvxLT}$TWLr;4Df?!IP3RGy
zM#yd(A=rV;)1$d-3WZ7ULgxBT3Q88ukU#tiY^-~Wl?B=vQyi)32#RgEiv8YpQbW?&
zd-sW%!C+!K@FZ5}RqfpLpm*2cArH7u01N$6yHhJUD=$tyE+{*wt`luh$BQzyA~V_q
z_5KKH*eRqph}Xn~nNNSkCd{|3s)OAmLab{P8GX$wdP&WL3zObVMIyeo*HS5CY4F$m
zctKda8OT@tJi@_p7_mvUDdy3Y-333&}l8I2vE%
zXODyKK-3G<(@!4pXZ0>=pdEf8t%BK;&4|6)3R;2XO{zS)D3l#k*@O@;{g(nk)U5Ri
z%NQ(hfkGv(?s2uR1$&ev-yL9eDyAE6A72%wgB6C-*utp!PsT`6V*XD(J>xCdkXB&-z2{6b`uWrV5&YiW2P>MQqSTR0
zKK*w||J2I^isiZwju^bzsC`B6(2`V|MlSV!U$K5^r!Ul5nINxuE^P~ccmv++G2nPR
z_wjQ6lhuArld1S-jg-{ZzqNC51E9k~{D%(}1KvNflEL)~3nykVcgGj}-DZ}8Ojc?3
zhx_!$=jIGD6gcsHBv^VGTDbqeZr0tWxx8qY)tL8A|37USamn3p2^p~DVo{kf>HmH<
zray|RdZ6j}*QjK0wip#72+nTl>gOH|j1E9jtd7efh~%?6
zszrnoNeGSKX+N)o8v>F>7~ZLQy?s{BA{m+N{~L8VwoKQVAM@U}nh_2szeU%m)J+JD
zy$&*LN35U9a3}RXT6F4F694b5-e2F{y?{qRmdfX7X;PoAerv6`S}zn3@zm16m-k&|mZ+CtX8a;QUaE@)${=zL)|ylf4h{r2gO+C(
zKCUNkj{OEe}m&In1{48iAO;V1Z
zOFEKVQNZhJJ-yoUvRI=GSH0L6S|jWp>)Acu;6bO*2ox!1Y6?_n)P({RzA>bD)8%;C
zRW_NP2FN9%bKr=1y~TC>evPLq%{*o)KG7TIVCVG9B~Jqonp$S--A~euszekaAtCFv
zavGpe736lb3c^lv*`Iz@E(m6KQq3>aj2J7_swqBQPoQR$H7^
z0^yJ>cPoF$Ztg$X7=g-#mjb3)On=$FMaRyv)8m?+A0t#>jKnF6<}NSaHlaq5z_ECV
zLeB&gUqL+1yCokrtMnyJ2k(1u(~S}-0CMQORFRthN1JQm&AIthyu>+@m_s$c&rB;w
z)jmfYm6%mp$5Zk%iI_|RWiHML-VCIRN=>Yhax9H68)zypt2dqW_pSHZQKcQ0P5j&Y
zgu#QZv
z5`&tqUHX$rINC9jY5}vJ@B6aiyj7C#Lt4r789BS-dO77Pb7dMDVva#!C~9akelf4*NDGvdbXYBX^EqYBpAXZGW5C(ovb92!
zzLv*H=dfY?oDEL-h+z-WfxQKtD3zw9PI0(jq8u!>#j?!2?Bu*SnE&N|6FfnKubE;!
zS2`K%<2999vyC)93Do5a;`+icLnp{u$18t$m2$eC%!fp_p9xr>E70{aYZs0GDDI}Z
zDRHm*4FONR<BgF1M#@Y6!^FlG2ct#~I_ZK%B}VT9;pP5w-nPT7-G1_-S6Ni$cWJ#o
z-9DT{G*~WM(9EpHO~WB!M?Bd%1!IIziMq#NQ~q=KZ!lmYT5Wkm;mBz`2PJ5MTMNGpw<9K_tckmm`+0$tK4TR=!Ib8f#
z=q@@{`gxF?21|Bt5ApvfV*>?x4;6gul+-M~Kh??SwO=|Q*Ya!*VF)=?2l@AqlP2H>
zSUzAmrRTQ>?1}yX8MRa((&x+l|3JdU{B_Fq
ziF0eC=dH{2_ZsCPSQx`%UJ`x}eA;m$@R=D#rht1^@_k*P+sTp?(6q%VH3Ho4AGg66
z`m^co)n@*F+euqO?1VyD>VQJ3{EttM^1Bm8^#muj8$da059lA+Pv<1Q8L9LouO&xw
z%@Ft`DBkS3Jq>y)_8LQgnAJI0_I{&*_zY&Lm&h6{x7ro)=oA=rxji7DlCZ{S3HlK1
zuh63YpC-)zr2}u0rt|sgy!#0{tm%P1JSm$6d)CJF4`yt7QT4Vj^Yeh>slmZy4WG{!
zLE^D&z9daXBVj@=`g|`}8|9|oLYUZj#c$9_`2R%Mgs!eb!QZ$V+R}Xg^WSck0N5Ak
ze2NfS3GHcKgFQk@mlGfcPI&CpJ$88C#KXYC(%7#yJE%kQsU~RbGqg=;O*ivE-msSz
zbR#_0i#d!fZC&$2xQw@9G1=S__`U!y6%Ku3HtPpzslnLnJlUk-*E08uXKc>%-WpP0*-N4wJ6J7#zu|Q|g8NL$
z7Y~$-O9KT2_=|xS!g}8aN;3``)#e=Ju8%te3Td1PkN6xhXkf>$VQqX1X@>m<&5jXC
z1%iLD6#>gflm#B83x>CYpS-+EA5O=bhyic|$L@1W6qs~4U$K(NsbC$6cXH9W)O14j
zz_IT@z;?;R>vCPJI)F)~SdcEq>&vfKz+8|*-uEJrT1AxO9oL6F(qNNzpF8wPppF1E
z2PmlvI%fedVxHcaj@Ssp0<%(w1*>g?>SX5=t<)}Z;S
za7#a>+3?3#m#0TD653@Gbr=fUo?bry?YTxXyS$Ux`c@x~L&2b&YCdKNbRL;cisUBa
zPxH83<7?0xCDUq9OYylMQ)oM6Bw5~db19tmtL0lhK0f>SboxMFBgnR#9h>$V3DwlhcBiyKvIRh6McH>~1==<5
zRRK0lCv8Retxmu+3`LPonElV49f@oo&Df3e*?Jut5E1!Uu9%cM|E+$NJ%iW%H4=yY
zvg!qwPiwt;$@}VBt?X^t6vmRsev!|trr;OK1Wsv9_CNAC?f9nro(xn@fj?B{bNkD$
z)qz6c$KB!F-kt3KJ)PnXFgHSv;kw>)iIfUil_ZUJ7r4mi(_{>1;*_W1@}EWYj0H+Y
zlkry7N0MnkUz;pWd^>N}a;?3$vsqn}r+(6E&?*;=ckEN@39G(Sl9axJ?6c7tRo2H1
ze1xA*1zKAt2T6A+xZe)O94j1h-Iv*eldUT3Ncmk7mwi{Jb=AX}xw-pb*libu&&)w^
zm{Ap~%DThJ53|xMTV+g05B~g~TXKi1ngFy+d51>BZB85|0{Db{ArcV&!Fcg#kPn!S0-hF6Y7^Qg~~y1AemGC
zzl>41p3?u*OE_6nafdS0M+;34`fZo5?k&+S$t2yy25r3PEkt=EDM1x4@fB|%Gh2zQ
z5@#6Y$hhCD5|jP7>{K=nwR3wE1cAaN`Sd<2$|i$pNRDyOz02JtaqFimMw}g!63~d|
z!8^2+2uw=jou3hXE`u@IIq?)xuaVJN6KT{`X&kOjgqY6`$tmD|j-^Kdt)a%>Oh6u|
zZw4BHVpiVyR{zUD3Pdr>w@>(GzNQ0;>YBMxDU5`tK>e5N?2~7Ip1lDdb;uwhd&M5>
zND_lxaBrSwOcFcFzSU4XMLsn@K`8zIIo?iQLsPTO(+kEKZ1&hk0w0f^)*(wjk;T;O
z)?y#I6rehR>>^4Mg*3bvW2+}Qak-&%iuRJNM5nIwqs#C0lQ0acQ9>|MtJ@E$2l~uB
zps7-d)Hk(1WA)%xigYJejsJt0LJYCcP@kxH%g0}2MvcoJpTdX|Adxujc3BKKY=uQC
ziG1jBI4-#G^m`eC>RPtjiHPA`E`@_c
z_3Cb__D5E1v1?Fwti}A%&G3O*`J&B8xY-tm4VUt*i`Cx5R99HI^;9i!g+bF44T#F9
zlb_vqgL#KNBNm5@!4c7Z>45KGwWLDT3kWJQcj!c#$5MV|KrGa<3syZVJRyiA=4(8V
zJNr3QVz8pw`ua~==XR!;i%kx39UK=Ja3r8NNMEE)rjVnQov53r@3ugO*+CfoHI+|Q
z?3fF~`xVH`zV!vf+33$(X8hxg-F)OZNrcpbH|M*bA;{=t9h}7NSRWecD!O9^z!N>T
zG}@nxr>ZHd$2TM&77F%HN3pKqE4m!o5gg(-ecG^7en`!H7tJP~6>r^x*4Wf%(I5+Cbl8j*D
zxovsxAG{puZMk5R)R(db520h0bEU4de9aXbf_*>3OCSkF@
zqu)h+r8Ac~X0;i5H`=3h%m_K{bFFPfF#k<3j47e|Ep{(mA-l!vVmDW*GZM)c(iZ!k
zX4soF=T5V24!DfEk^m7-huN6Z6L1x)vhDe`<=Ca|3XYGbPpDsYqmR{dSiW(TC=b2P
zye1m0<}*1Z_l;9qOITsl9V0wqov{Wdx83FZLMuFn#)2)6|ApxEKdi^7<
zxTiyHz-ztE`}aMs*>F
z6Q6-92IlW`64p4tCem+zCq4e<`>V(g13^{eT$;-#5>o)*r)BoJ>~F
zi^x@R>p5S5CeJ|%pU#*0&)in{{2#t+zD@euR;h_Y{H2Z?QMg#DklC1Od0@_|XRcrH4TERx^+
zn!BZy(*MaE)Zajnk^poAFt0-e)J=fdr8I$o22!HWJNt?eSllD7#%3y
zuahpr>NTD}s3Q(^W3VyzOJDwKaI@;B2%f(A{*<@zX#^fs@-6%)vqSv-bP{a`tQjE(f4gb+EPsT9(lL?T1+tQla~2FP1T=#<|{Ki@9g_m7=?@6^5CJ=;ke
z$XjA@s4@SphztFpal3qI1^(Wl{11)$f88^W^p82$1(Qhrnp5)+jSC+M_jhF~_79Eg
zkS!bgtY&CBK!_R`iNNQTfIX={8CH`LNb0ZnIDin^0dGW}SA-#N|IoNAMbmGeP1}tO
z2vIH^NA|ozcKe6My**ajk9$sij6(n+G;^fCJg;Cp{Go9lE=wSPl~XO+Z6-SrM>s)!
z%oIlRP$jjOYh)Z=I`PA^atp)nWIqbSsh0d%ZHUlX7y$coe%~+GI)FTuL
za+DIaUadC7pd>MoEhI&@>GbIBt<8d6>*+PT#&?43PR97e_`&mcMo+>7&A$=sx{l;t
zV|uCvc}J93#{|<(5U=C0`wlWJyZnCDAG0rM7g>};yM$HTT-TI6GpWF}hoYC!$(Rw$
znc*ofv*j`Q*@FGucKY^YG~{EuI1Vy=FT2t`@2@>Ayh6WOLEp-
zTgX0LKZS6p{v$e20NRmNiVBpWDbzzDsJSW3W4A3sr?~o9bifl>!HB5ue*22J>nUSk
zR$7Tp*E}D!La1QS_Lc7F@b@J?Us|Ib^z9i^*O{44H=bhmkXhQJ=Gbi7Hn}DB`VJpA
z-A6NXS*-O0=J%I!JN5P3>9*|Yeakp68(HlHo>DohKRQSGM7QE7DHeJC%(bhiE2)Ka
zb=p*wU1n}JkJ;6!CCNKkt*-H5zsq+di2EPf13X<^2BvC^fzjI6ThsCDF9q=gv>*8m
zMWoZ4E55U_Nr{BE4^y=#LPia@$F)z0LJZZ*^>j&rk%ci8XZg{#NT$>NrB)j8q;#Od
z!gEHITp=Zzog&|HICXwg{WF(Xk+4cZ&=h|=JY53r6P}1maE0I(*{aaiNb~Tr{2?Lc
za77`o){2uazh$uL&UD9aB8yBphRJ_?UYr<)QOnCe&u$1?)~ckZPw7jtM8Xu4t;$q9
zqe?X^OQ|le*lSOc>-hKqx1!YYZrfW{d
z_1n`}Upr42$9OOO;|ER)H1&OVITngtZkyTboG3{Kkd-}^jRwB;xc11ZCqD!+uA^&y
z#0YEM@@!TS)tu#QuZ6mC=6m1Trb3|EDE`-D|Cqf`Px##F>tZ5TM9SGtNq?U@%_7ng
z{SFTbtJj^vvilAaAK7{V|69e$VXWB1*v|{-FZLYVe8>=&
zoy`IRuh%w>##hJC;m<-0eR|~7L!tj1Jpsj1z$p{EQns(?HLdod@lv?+QvDL#@6Qq|_^T27x5()j
z+;yyUyX*Qsf*^UAX