From 74f30dcf8e0284d2e09deba714244df4195c0b57 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Tue, 4 Oct 2022 12:25:25 +0200
Subject: [PATCH 01/39] Move Cloud Integrations out of the `cloud` plugin
(#141103)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../steps/storybooks/build_and_upload.ts | 2 +-
docs/developer/plugin-list.asciidoc | 12 +
packages/kbn-optimizer/limits.yml | 3 +
src/dev/storybook/aliases.ts | 2 +-
src/plugins/home/kibana.json | 2 +-
src/plugins/home/public/plugin.test.ts | 98 ++--
src/plugins/home/public/plugin.ts | 23 +-
.../services/environment/environment.mock.ts | 3 +-
src/plugins/home/tsconfig.json | 3 +-
test/plugin_functional/config.ts | 8 +
.../test_suites/core_plugins/rendering.ts | 13 +-
tsconfig.base.json | 6 +
x-pack/.i18nrc.json | 2 +
x-pack/plugins/cloud/common/constants.ts | 1 -
x-pack/plugins/cloud/kibana.json | 2 +-
x-pack/plugins/cloud/public/index.ts | 2 -
x-pack/plugins/cloud/public/mocks.tsx | 21 +-
x-pack/plugins/cloud/public/plugin.test.ts | 517 +-----------------
x-pack/plugins/cloud/public/plugin.tsx | 340 +++---------
x-pack/plugins/cloud/server/config.ts | 23 -
x-pack/plugins/cloud/server/mocks.ts | 25 +
x-pack/plugins/cloud/server/plugin.test.ts | 125 ++---
x-pack/plugins/cloud/server/plugin.ts | 50 +-
x-pack/plugins/cloud/tsconfig.json | 3 -
.../cloud_chat}/.storybook/decorator.tsx | 5 +-
.../cloud_chat}/.storybook/index.ts | 0
.../cloud_chat}/.storybook/main.ts | 0
.../cloud_chat}/.storybook/manager.ts | 0
.../cloud_chat}/.storybook/preview.ts | 0
.../cloud_integrations/cloud_chat/README.md | 3 +
.../cloud_chat/common/constants.ts | 8 +
.../cloud_chat}/common/types.ts | 0
.../cloud_chat/jest.config.js | 18 +
.../cloud_integrations/cloud_chat/kibana.json | 15 +
.../public/components/chat/chat.stories.tsx | 1 -
.../public/components/chat/chat.tsx | 4 +-
.../components/chat/get_chat_context.test.ts | 2 +-
.../components/chat/get_chat_context.ts | 0
.../public/components/chat/index.ts | 0
.../public/components/chat/use_chat_config.ts | 8 +-
.../cloud_chat}/public/components/index.tsx | 0
.../cloud_chat/public/index.ts | 15 +
.../cloud_chat/public/plugin.test.ts | 103 ++++
.../cloud_chat/public/plugin.tsx | 88 +++
.../cloud_chat}/public/services/index.tsx | 21 +-
.../cloud_chat/server/config.ts | 74 +++
.../cloud_chat/server/index.ts | 15 +
.../cloud_chat/server/plugin.ts | 43 ++
.../cloud_chat}/server/routes/chat.test.ts | 0
.../cloud_chat}/server/routes/chat.ts | 0
.../cloud_chat/server/routes/index.ts | 8 +
.../server/util/generate_jwt.test.ts | 0
.../cloud_chat}/server/util/generate_jwt.ts | 0
.../cloud_chat/tsconfig.json | 21 +
.../cloud_experiments/common/index.ts | 1 -
.../cloud_experiments/common/mocks.ts | 9 +-
.../cloud_experiments/common/types.ts | 21 -
.../cloud_experiments/kibana.json | 2 +-
.../cloud_experiments/public/plugin.test.ts | 78 ++-
.../cloud_experiments/public/plugin.ts | 53 +-
.../cloud_experiments/server/plugin.test.ts | 58 +-
.../cloud_experiments/server/plugin.ts | 35 +-
.../cloud_experiments/tsconfig.json | 1 +
.../cloud_full_story/.i18nrc.json | 7 +
.../cloud_full_story/README.md | 3 +
.../cloud_full_story/jest.config.js | 18 +
.../cloud_full_story/kibana.json | 15 +
.../cloud_full_story/public/index.ts | 13 +
.../cloud_full_story/public/plugin.test.ts | 70 +++
.../cloud_full_story/public/plugin.ts | 75 +++
.../server/assets/fullstory_library.js | 0
.../cloud_full_story}/server/config.test.ts | 18 +-
.../cloud_full_story/server/config.ts | 82 +++
.../cloud_full_story/server/index.ts | 15 +
.../server/plugin.test.mock.ts | 12 +
.../cloud_full_story/server/plugin.test.ts | 33 ++
.../cloud_full_story/server/plugin.ts | 32 ++
.../server/routes/fullstory.test.ts | 0
.../server/routes/fullstory.ts | 2 +-
.../cloud_full_story/server/routes/index.ts | 8 +
.../cloud_full_story/tsconfig.json | 20 +
.../cloud_integrations/cloud_links/README.md | 3 +
.../cloud_links/jest.config.js | 18 +
.../cloud_links/kibana.json | 14 +
.../cloud_links/public/index.ts | 12 +
.../public/maybe_add_cloud_links/index.ts | 8 +
.../maybe_add_cloud_links.test.ts | 134 +++++
.../maybe_add_cloud_links.ts | 48 ++
.../maybe_add_cloud_links}/user_menu_links.ts | 21 +-
.../cloud_links/public/plugin.test.mocks.ts | 12 +
.../cloud_links/public/plugin.test.ts | 77 +++
.../cloud_links/public/plugin.ts | 39 ++
.../cloud_links/tsconfig.json | 21 +
x-pack/plugins/data_visualizer/kibana.json | 2 +-
.../file_data_visualizer_view.js | 2 +-
x-pack/plugins/data_visualizer/tsconfig.json | 1 +
x-pack/plugins/enterprise_search/kibana.json | 4 +-
.../__mocks__/kea_logic/kibana_logic.mock.ts | 2 +
.../product_selector/product_selector.tsx | 2 +-
.../plugins/enterprise_search/tsconfig.json | 1 +
.../plugins/fleet/.storybook/context/cloud.ts | 1 +
x-pack/plugins/fleet/kibana.json | 2 +-
.../public/applications/integrations/app.tsx | 2 +-
x-pack/plugins/fleet/tsconfig.json | 1 +
.../cloud_aware_behavior.test.ts | 3 +-
.../features/searchable_snapshots.test.ts | 5 +-
x-pack/plugins/security/kibana.json | 2 +-
.../analytics/analytics_service.test.ts | 43 +-
.../public/analytics/analytics_service.ts | 13 +-
.../analytics/register_user_context.test.ts | 126 +++++
.../public/analytics/register_user_context.ts | 67 +++
x-pack/plugins/security/public/plugin.tsx | 12 +-
x-pack/plugins/security/server/plugin.test.ts | 9 -
x-pack/plugins/security/server/plugin.ts | 28 +-
x-pack/plugins/security/tsconfig.json | 1 +
.../translations/translations/fr-FR.json | 5 -
.../translations/translations/ja-JP.json | 5 -
.../translations/translations/zh-CN.json | 5 -
118 files changed, 1907 insertions(+), 1263 deletions(-)
create mode 100644 x-pack/plugins/cloud/server/mocks.ts
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/decorator.tsx (89%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/index.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/main.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/manager.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/preview.ts (100%)
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/README.md
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/common/types.ts (100%)
create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/kibana.json
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/chat.stories.tsx (99%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/chat.tsx (94%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/get_chat_context.test.ts (97%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/get_chat_context.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/index.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/use_chat_config.ts (95%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/index.tsx (100%)
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/public/index.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/services/index.tsx (58%)
create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/server/config.ts
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/server/index.ts
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/routes/chat.test.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/routes/chat.ts (100%)
create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/server/routes/index.ts
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/util/generate_jwt.test.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/util/generate_jwt.ts (100%)
create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/.i18nrc.json
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/README.md
create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/jest.config.js
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/kibana.json
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/public/index.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.test.ts
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts
rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/assets/fullstory_library.js (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/config.test.ts (50%)
create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/server/index.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.mock.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.ts
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.ts
rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/routes/fullstory.test.ts (100%)
rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/routes/fullstory.ts (98%)
create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/index.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json
create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/README.md
create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/jest.config.js
create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/kibana.json
create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/public/index.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/index.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts
rename x-pack/plugins/{cloud/public => cloud_integrations/cloud_links/public/maybe_add_cloud_links}/user_menu_links.ts (50%)
create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.mocks.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts
create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts
create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json
create mode 100644 x-pack/plugins/security/public/analytics/register_user_context.test.ts
create mode 100644 x-pack/plugins/security/public/analytics/register_user_context.ts
diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts
index dcceca7848910..945f85a820971 100644
--- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts
+++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts
@@ -15,7 +15,7 @@ const STORYBOOKS = [
'apm',
'canvas',
'ci_composite',
- 'cloud',
+ 'cloud_chat',
'coloring',
'chart_icons',
'controls',
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index f4fc9c67508ef..407261c6f1d7e 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -424,10 +424,22 @@ The plugin exposes the static DefaultEditorController class to consume.
|The cloud plugin adds Cloud-specific features to Kibana.
+|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_chat/README.md[cloudChat]
+|Integrates with DriftChat in order to provide live support to our Elastic Cloud users. This plugin should only run on Elastic Cloud.
+
+
|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_experiments/README.mdx[cloudExperiments]
|The Cloud Experiments Service provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments.
+|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_full_story/README.md[cloudFullStory]
+|Integrates with FullStory in order to provide better product analytics, so we can understand how our users make use of Kibana. This plugin should only run on Elastic Cloud.
+
+
+|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_links/README.md[cloudLinks]
+|Adds all the links to the Elastic Cloud console.
+
+
|{kib-repo}blob/{branch}/x-pack/plugins/cloud_security_posture/README.md[cloudSecurityPosture]
|Cloud Posture automates the identification and remediation of risks across cloud infrastructures
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 5cd1458028626..67064af8cddc5 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -10,7 +10,10 @@ pageLoadAssetSize:
cases: 144442
charts: 55000
cloud: 21076
+ cloudChat: 19894
cloudExperiments: 59358
+ cloudFullStory: 18493
+ cloudLinks: 17629
cloudSecurityPosture: 19109
console: 46091
controls: 40000
diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts
index b4224e154def5..6f82ec078f7ab 100644
--- a/src/dev/storybook/aliases.ts
+++ b/src/dev/storybook/aliases.ts
@@ -11,7 +11,7 @@ export const storybookAliases = {
apm: 'x-pack/plugins/apm/.storybook',
canvas: 'x-pack/plugins/canvas/storybook',
ci_composite: '.ci/.storybook',
- cloud: 'x-pack/plugins/cloud/.storybook',
+ cloud_chat: 'x-pack/plugins/cloud_integrations/cloud_chat/.storybook',
coloring: 'packages/kbn-coloring/.storybook',
chart_icons: 'packages/kbn-chart-icons/.storybook',
content_management: 'packages/content-management/.storybook',
diff --git a/src/plugins/home/kibana.json b/src/plugins/home/kibana.json
index 02b33e814e2a1..72b4d6cb8fd0b 100644
--- a/src/plugins/home/kibana.json
+++ b/src/plugins/home/kibana.json
@@ -8,6 +8,6 @@
"server": true,
"ui": true,
"requiredPlugins": ["dataViews", "share", "urlForwarding"],
- "optionalPlugins": ["usageCollection", "customIntegrations"],
+ "optionalPlugins": ["usageCollection", "customIntegrations", "cloud"],
"requiredBundles": ["kibanaReact"]
}
diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts
index 12243944ef0f0..a6c6012a28ed6 100644
--- a/src/plugins/home/public/plugin.test.ts
+++ b/src/plugins/home/public/plugin.test.ts
@@ -11,6 +11,7 @@ import { HomePublicPlugin } from './plugin';
import { coreMock } from '@kbn/core/public/mocks';
import { urlForwardingPluginMock } from '@kbn/url-forwarding-plugin/public/mocks';
import { SharePluginSetup } from '@kbn/share-plugin/public';
+import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
const mockInitializerContext = coreMock.createPluginInitializerContext();
const mockShare = {} as SharePluginSetup;
@@ -24,14 +25,11 @@ describe('HomePublicPlugin', () => {
});
describe('setup', () => {
- test('registers tutorial directory to feature catalogue', async () => {
- const setup = await new HomePublicPlugin(mockInitializerContext).setup(
- coreMock.createSetup() as any,
- {
- share: mockShare,
- urlForwarding: urlForwardingPluginMock.createSetupContract(),
- }
- );
+ test('registers tutorial directory to feature catalogue', () => {
+ const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), {
+ share: mockShare,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ });
expect(setup).toHaveProperty('featureCatalogue');
expect(setup.featureCatalogue.register).toHaveBeenCalledTimes(1);
expect(setup.featureCatalogue.register).toHaveBeenCalledWith(
@@ -44,53 +42,73 @@ describe('HomePublicPlugin', () => {
);
});
- test('wires up and returns registry', async () => {
- const setup = await new HomePublicPlugin(mockInitializerContext).setup(
- coreMock.createSetup() as any,
- {
- share: mockShare,
- urlForwarding: urlForwardingPluginMock.createSetupContract(),
- }
- );
+ test('wires up and returns registry', () => {
+ const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), {
+ share: mockShare,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ });
expect(setup).toHaveProperty('featureCatalogue');
expect(setup.featureCatalogue).toHaveProperty('register');
});
- test('wires up and returns environment service', async () => {
- const setup = await new HomePublicPlugin(mockInitializerContext).setup(
- coreMock.createSetup() as any,
- {
- share: {} as SharePluginSetup,
- urlForwarding: urlForwardingPluginMock.createSetupContract(),
- }
- );
+ test('wires up and returns environment service', () => {
+ const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), {
+ share: {} as SharePluginSetup,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ });
expect(setup).toHaveProperty('environment');
expect(setup.environment).toHaveProperty('update');
});
- test('wires up and returns tutorial service', async () => {
- const setup = await new HomePublicPlugin(mockInitializerContext).setup(
- coreMock.createSetup() as any,
- {
- share: mockShare,
- urlForwarding: urlForwardingPluginMock.createSetupContract(),
- }
- );
+ test('wires up and returns tutorial service', () => {
+ const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), {
+ share: mockShare,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ });
expect(setup).toHaveProperty('tutorials');
expect(setup.tutorials).toHaveProperty('setVariable');
});
- test('wires up and returns welcome service', async () => {
- const setup = await new HomePublicPlugin(mockInitializerContext).setup(
- coreMock.createSetup() as any,
- {
- share: mockShare,
- urlForwarding: urlForwardingPluginMock.createSetupContract(),
- }
- );
+ test('wires up and returns welcome service', () => {
+ const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), {
+ share: mockShare,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ });
expect(setup).toHaveProperty('welcomeScreen');
expect(setup.welcomeScreen).toHaveProperty('registerOnRendered');
expect(setup.welcomeScreen).toHaveProperty('registerTelemetryNoticeRenderer');
});
+
+ test('sets the cloud environment variable when the cloud plugin is present but isCloudEnabled: false', () => {
+ const cloud = { ...cloudMock.createSetup(), isCloudEnabled: false };
+ const plugin = new HomePublicPlugin(mockInitializerContext);
+ const setup = plugin.setup(coreMock.createSetup(), {
+ cloud,
+ share: mockShare,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ });
+ expect(setup.environment.update).toHaveBeenCalledTimes(1);
+ expect(setup.environment.update).toHaveBeenCalledWith({ cloud: false });
+ expect(setup.tutorials.setVariable).toHaveBeenCalledTimes(0);
+ });
+
+ test('when cloud is enabled, it sets the cloud environment and the tutorials variable "cloud"', () => {
+ const cloud = { ...cloudMock.createSetup(), isCloudEnabled: true };
+ const plugin = new HomePublicPlugin(mockInitializerContext);
+ const setup = plugin.setup(coreMock.createSetup(), {
+ cloud,
+ share: mockShare,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ });
+ expect(setup.environment.update).toHaveBeenCalledTimes(1);
+ expect(setup.environment.update).toHaveBeenCalledWith({ cloud: true });
+ expect(setup.tutorials.setVariable).toHaveBeenCalledTimes(1);
+ expect(setup.tutorials.setVariable).toHaveBeenCalledWith('cloud', {
+ id: 'mock-cloud-id',
+ baseUrl: 'base-url',
+ deploymentUrl: 'deployment-url',
+ profileUrl: 'profile-url',
+ });
+ });
});
});
diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts
index 642a8d575e078..e27ddf107a5ee 100644
--- a/src/plugins/home/public/plugin.ts
+++ b/src/plugins/home/public/plugin.ts
@@ -20,6 +20,7 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
import { AppNavLinkStatus } from '@kbn/core/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
+import type { CloudSetup } from '@kbn/cloud-plugin/public';
import { PLUGIN_ID, HOME_APP_BASE_PATH } from '../common/constants';
import { setServices } from './application/kibana_services';
import { ConfigSchema } from '../config';
@@ -42,6 +43,7 @@ export interface HomePluginStartDependencies {
}
export interface HomePluginSetupDependencies {
+ cloud?: CloudSetup;
share: SharePluginSetup;
usageCollection?: UsageCollectionSetup;
urlForwarding: UrlForwardingSetup;
@@ -66,7 +68,7 @@ export class HomePublicPlugin
public setup(
core: CoreSetup,
- { share, urlForwarding, usageCollection }: HomePluginSetupDependencies
+ { cloud, share, urlForwarding, usageCollection }: HomePluginSetupDependencies
): HomePublicPluginSetup {
core.application.register({
id: PLUGIN_ID,
@@ -127,10 +129,25 @@ export class HomePublicPlugin
order: 500,
});
+ const environment = { ...this.environmentService.setup() };
+ const tutorials = { ...this.tutorialService.setup() };
+ if (cloud) {
+ environment.update({ cloud: cloud.isCloudEnabled });
+ if (cloud.isCloudEnabled) {
+ tutorials.setVariable('cloud', {
+ id: cloud.cloudId,
+ baseUrl: cloud.baseUrl,
+ // Cloud's API already provides the full URLs
+ profileUrl: cloud.profileUrl?.replace(cloud.baseUrl ?? '', ''),
+ deploymentUrl: cloud.deploymentUrl?.replace(cloud.baseUrl ?? '', ''),
+ });
+ }
+ }
+
return {
featureCatalogue,
- environment: { ...this.environmentService.setup() },
- tutorials: { ...this.tutorialService.setup() },
+ environment,
+ tutorials,
addData: { ...this.addDataService.setup() },
welcomeScreen: { ...this.welcomeService.setup() },
};
diff --git a/src/plugins/home/public/services/environment/environment.mock.ts b/src/plugins/home/public/services/environment/environment.mock.ts
index 713a59ceac7bf..f2d4747d44d6a 100644
--- a/src/plugins/home/public/services/environment/environment.mock.ts
+++ b/src/plugins/home/public/services/environment/environment.mock.ts
@@ -18,14 +18,13 @@ const createSetupMock = (): jest.Mocked => {
const createMock = (): jest.Mocked> => {
const service = {
- setup: jest.fn(),
+ setup: jest.fn(createSetupMock),
getEnvironment: jest.fn(() => ({
cloud: false,
apmUi: false,
ml: false,
})),
};
- service.setup.mockImplementation(createSetupMock);
return service;
};
diff --git a/src/plugins/home/tsconfig.json b/src/plugins/home/tsconfig.json
index 8e617896e3f96..af121720eee0e 100644
--- a/src/plugins/home/tsconfig.json
+++ b/src/plugins/home/tsconfig.json
@@ -15,6 +15,7 @@
{ "path": "../kibana_react/tsconfig.json" },
{ "path": "../share/tsconfig.json" },
{ "path": "../url_forwarding/tsconfig.json" },
- { "path": "../usage_collection/tsconfig.json" }
+ { "path": "../usage_collection/tsconfig.json" },
+ { "path": "../../../x-pack/plugins/cloud/tsconfig.json" }
]
}
diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts
index b2dbc762ab657..750da63e27d1c 100644
--- a/test/plugin_functional/config.ts
+++ b/test/plugin_functional/config.ts
@@ -60,6 +60,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
'--corePluginDeprecations.noLongerUsed=still_using',
// for testing set buffer duration to 0 to immediately flush counters into saved objects.
'--usageCollection.usageCounters.bufferDuration=0',
+ // explicitly enable the cloud integration plugins to validate the rendered config keys
+ '--xpack.cloud_integrations.chat.enabled=true',
+ '--xpack.cloud_integrations.chat.chatURL=a_string',
+ '--xpack.cloud_integrations.experiments.enabled=true',
+ '--xpack.cloud_integrations.experiments.launch_darkly.sdk_key=a_string',
+ '--xpack.cloud_integrations.experiments.launch_darkly.client_id=a_string',
+ '--xpack.cloud_integrations.full_story.enabled=true',
+ '--xpack.cloud_integrations.full_story.org_id=a_string',
...plugins.map(
(pluginDir) => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}`
),
diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts
index cbc98ec7bb07b..4633a374ee9d5 100644
--- a/test/plugin_functional/test_suites/core_plugins/rendering.ts
+++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts
@@ -171,14 +171,17 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.cases.markdownPlugins.lens (boolean)',
'xpack.ccr.ui.enabled (boolean)',
'xpack.cloud.base_url (string)',
- 'xpack.cloud.chat.chatURL (string)',
- 'xpack.cloud.chat.enabled (boolean)',
'xpack.cloud.cname (string)',
'xpack.cloud.deployment_url (string)',
- 'xpack.cloud.full_story.enabled (boolean)',
- 'xpack.cloud.full_story.org_id (any)',
+ 'xpack.cloud_integrations.chat.chatURL (string)',
+ // No PII. This is an escape patch to override LaunchDarkly's flag resolution mechanism for testing or quick fix.
+ 'xpack.cloud_integrations.experiments.flag_overrides (record)',
+ // Commented because it's inside a schema conditional, and the test is not able to resolve it. But it's shared.
+ // Added here for documentation purposes.
+ // 'xpack.cloud_integrations.experiments.launch_darkly.client_id (string)',
+ 'xpack.cloud_integrations.full_story.org_id (any)',
// No PII. Just the list of event types we want to forward to FullStory.
- 'xpack.cloud.full_story.eventTypesAllowlist (array)',
+ 'xpack.cloud_integrations.full_story.eventTypesAllowlist (array)',
'xpack.cloud.id (string)',
'xpack.cloud.organization_url (string)',
'xpack.cloud.profile_url (string)',
diff --git a/tsconfig.base.json b/tsconfig.base.json
index b62beb6650448..3054a36f2bb86 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -313,8 +313,14 @@
"@kbn/canvas-plugin/*": ["x-pack/plugins/canvas/*"],
"@kbn/cases-plugin": ["x-pack/plugins/cases"],
"@kbn/cases-plugin/*": ["x-pack/plugins/cases/*"],
+ "@kbn/cloud-chat-plugin": ["x-pack/plugins/cloud_integrations/cloud_chat"],
+ "@kbn/cloud-chat-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_chat/*"],
"@kbn/cloud-experiments-plugin": ["x-pack/plugins/cloud_integrations/cloud_experiments"],
"@kbn/cloud-experiments-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_experiments/*"],
+ "@kbn/cloud-full-story-plugin": ["x-pack/plugins/cloud_integrations/cloud_full_story"],
+ "@kbn/cloud-full-story-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_full_story/*"],
+ "@kbn/cloud-links-plugin": ["x-pack/plugins/cloud_integrations/cloud_links"],
+ "@kbn/cloud-links-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_links/*"],
"@kbn/cloud-security-posture-plugin": ["x-pack/plugins/cloud_security_posture"],
"@kbn/cloud-security-posture-plugin/*": ["x-pack/plugins/cloud_security_posture/*"],
"@kbn/cloud-plugin": ["x-pack/plugins/cloud"],
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index 83466ba749605..4f89798c71faf 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -10,6 +10,8 @@
"xpack.canvas": "plugins/canvas",
"xpack.cases": "plugins/cases",
"xpack.cloud": "plugins/cloud",
+ "xpack.cloudChat": "plugins/cloud_integrations/cloud_chat",
+ "xpack.cloudLinks": "plugins/cloud_integrations/cloud_links",
"xpack.csp": "plugins/cloud_security_posture",
"xpack.dashboard": "plugins/dashboard_enhanced",
"xpack.discover": "plugins/discover_enhanced",
diff --git a/x-pack/plugins/cloud/common/constants.ts b/x-pack/plugins/cloud/common/constants.ts
index 09333e3773fe9..fc37906299d14 100644
--- a/x-pack/plugins/cloud/common/constants.ts
+++ b/x-pack/plugins/cloud/common/constants.ts
@@ -6,7 +6,6 @@
*/
export const ELASTIC_SUPPORT_LINK = 'https://cloud.elastic.co/support';
-export const GET_CHAT_USER_DATA_ROUTE_PATH = '/internal/cloud/chat_user';
/**
* This is the page for managing your snapshots on Cloud.
diff --git a/x-pack/plugins/cloud/kibana.json b/x-pack/plugins/cloud/kibana.json
index 51df5d20d81b9..85434abc87ede 100644
--- a/x-pack/plugins/cloud/kibana.json
+++ b/x-pack/plugins/cloud/kibana.json
@@ -7,7 +7,7 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "cloud"],
- "optionalPlugins": ["cloudExperiments", "usageCollection", "home", "security"],
+ "optionalPlugins": ["usageCollection"],
"server": true,
"ui": true
}
diff --git a/x-pack/plugins/cloud/public/index.ts b/x-pack/plugins/cloud/public/index.ts
index d50798cb15cd2..ee37f85dfb6a7 100644
--- a/x-pack/plugins/cloud/public/index.ts
+++ b/x-pack/plugins/cloud/public/index.ts
@@ -13,5 +13,3 @@ export type { CloudSetup, CloudConfigType, CloudStart } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new CloudPlugin(initializerContext);
}
-
-export { Chat } from './components';
diff --git a/x-pack/plugins/cloud/public/mocks.tsx b/x-pack/plugins/cloud/public/mocks.tsx
index f31596f3930f5..608e826657b73 100644
--- a/x-pack/plugins/cloud/public/mocks.tsx
+++ b/x-pack/plugins/cloud/public/mocks.tsx
@@ -8,7 +8,6 @@
import React from 'react';
import { CloudStart } from '.';
-import { ServicesProvider } from './services';
function createSetupMock() {
return {
@@ -19,28 +18,22 @@ function createSetupMock() {
deploymentUrl: 'deployment-url',
profileUrl: 'profile-url',
organizationUrl: 'organization-url',
+ registerCloudService: jest.fn(),
};
}
-const config = {
- chat: {
- enabled: true,
- chatURL: 'chat-url',
- user: {
- id: 'user-id',
- email: 'test-user@elastic.co',
- jwt: 'identity-jwt',
- },
- },
-};
-
const getContextProvider: () => React.FC =
() =>
({ children }) =>
- {children};
+ <>{children}>;
const createStartMock = (): jest.Mocked => ({
CloudContextProvider: jest.fn(getContextProvider()),
+ cloudId: 'mock-cloud-id',
+ isCloudEnabled: true,
+ deploymentUrl: 'deployment-url',
+ profileUrl: 'profile-url',
+ organizationUrl: 'organization-url',
});
export const cloudMock = {
diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts
index 599dee5e707b7..efb566761e22a 100644
--- a/x-pack/plugins/cloud/public/plugin.test.ts
+++ b/x-pack/plugins/cloud/public/plugin.test.ts
@@ -5,308 +5,18 @@
* 2.0.
*/
-import { firstValueFrom } from 'rxjs';
-import { Sha256 } from '@kbn/crypto-browser';
-import { nextTick } from '@kbn/test-jest-helpers';
import { coreMock } from '@kbn/core/public/mocks';
-import { homePluginMock } from '@kbn/home-plugin/public/mocks';
-import { securityMock } from '@kbn/security-plugin/public/mocks';
-import { CloudPlugin, type CloudConfigType } from './plugin';
-import { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common';
-import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks';
+import { CloudPlugin } from './plugin';
const baseConfig = {
base_url: 'https://cloud.elastic.co',
deployment_url: '/abc123',
profile_url: '/user/settings/',
organization_url: '/account/',
- full_story: {
- enabled: false,
- },
- chat: {
- enabled: false,
- },
};
describe('Cloud Plugin', () => {
describe('#setup', () => {
- describe('setupFullStory', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- const setupPlugin = async ({ config = {} }: { config?: Partial }) => {
- const initContext = coreMock.createPluginInitializerContext({
- ...baseConfig,
- id: 'cloudId',
- ...config,
- });
-
- const plugin = new CloudPlugin(initContext);
-
- const coreSetup = coreMock.createSetup();
-
- const setup = plugin.setup(coreSetup, {});
-
- // Wait for FullStory dynamic import to resolve
- await new Promise((r) => setImmediate(r));
-
- return { initContext, plugin, setup, coreSetup };
- };
-
- test('register the shipper FullStory with correct args when enabled and org_id are set', async () => {
- const { coreSetup } = await setupPlugin({
- config: { full_story: { enabled: true, org_id: 'foo' } },
- });
-
- expect(coreSetup.analytics.registerShipper).toHaveBeenCalled();
- expect(coreSetup.analytics.registerShipper).toHaveBeenCalledWith(expect.anything(), {
- fullStoryOrgId: 'foo',
- scriptUrl: '/internal/cloud/100/fullstory.js',
- namespace: 'FSKibana',
- });
- });
-
- it('does not call initializeFullStory when enabled=false', async () => {
- const { coreSetup } = await setupPlugin({
- config: { full_story: { enabled: false, org_id: 'foo' } },
- });
- expect(coreSetup.analytics.registerShipper).not.toHaveBeenCalled();
- });
-
- it('does not call initializeFullStory when org_id is undefined', async () => {
- const { coreSetup } = await setupPlugin({ config: { full_story: { enabled: true } } });
- expect(coreSetup.analytics.registerShipper).not.toHaveBeenCalled();
- });
- });
-
- describe('setupTelemetryContext', () => {
- const username = '1234';
- const expectedHashedPlainUsername = new Sha256().update(username, 'utf8').digest('hex');
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- const setupPlugin = async ({
- config = {},
- securityEnabled = true,
- currentUserProps = {},
- }: {
- config?: Partial;
- securityEnabled?: boolean;
- currentUserProps?: Record | Error;
- }) => {
- const initContext = coreMock.createPluginInitializerContext({
- ...baseConfig,
- ...config,
- });
-
- const plugin = new CloudPlugin(initContext);
-
- const coreSetup = coreMock.createSetup();
- const securitySetup = securityMock.createSetup();
- if (currentUserProps instanceof Error) {
- securitySetup.authc.getCurrentUser.mockRejectedValue(currentUserProps);
- } else {
- securitySetup.authc.getCurrentUser.mockResolvedValue(
- securityMock.createMockAuthenticatedUser(currentUserProps)
- );
- }
-
- const setup = plugin.setup(coreSetup, securityEnabled ? { security: securitySetup } : {});
-
- return { initContext, plugin, setup, coreSetup };
- };
-
- test('register the context provider for the cloud user with hashed user ID when security is available', async () => {
- const { coreSetup } = await setupPlugin({
- config: { id: 'cloudId' },
- currentUserProps: { username },
- });
-
- expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled();
-
- const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find(
- ([{ name }]) => name === 'cloud_user_id'
- )!;
-
- await expect(firstValueFrom(context$)).resolves.toEqual({
- userId: '5ef112cfdae3dea57097bc276e275b2816e73ef2a398dc0ffaf5b6b4e3af2041',
- isElasticCloudUser: false,
- });
- });
-
- it('user hash includes cloud id', async () => {
- const { coreSetup: coreSetup1 } = await setupPlugin({
- config: { id: 'esOrg1' },
- currentUserProps: { username },
- });
-
- const [{ context$: context1$ }] =
- coreSetup1.analytics.registerContextProvider.mock.calls.find(
- ([{ name }]) => name === 'cloud_user_id'
- )!;
-
- const { userId: hashId1 } = (await firstValueFrom(context1$)) as { userId: string };
- expect(hashId1).not.toEqual(expectedHashedPlainUsername);
-
- const { coreSetup: coreSetup2 } = await setupPlugin({
- config: { full_story: { enabled: true, org_id: 'foo' }, id: 'esOrg2' },
- currentUserProps: { username },
- });
-
- const [{ context$: context2$ }] =
- coreSetup2.analytics.registerContextProvider.mock.calls.find(
- ([{ name }]) => name === 'cloud_user_id'
- )!;
-
- const { userId: hashId2 } = (await firstValueFrom(context2$)) as { userId: string };
- expect(hashId2).not.toEqual(expectedHashedPlainUsername);
-
- expect(hashId1).not.toEqual(hashId2);
- });
-
- test('user hash does not include cloudId when user is an Elastic Cloud user', async () => {
- const { coreSetup } = await setupPlugin({
- config: { id: 'cloudDeploymentId' },
- currentUserProps: { username, elastic_cloud_user: true },
- });
-
- expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled();
-
- const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find(
- ([{ name }]) => name === 'cloud_user_id'
- )!;
-
- await expect(firstValueFrom(context$)).resolves.toEqual({
- userId: expectedHashedPlainUsername,
- isElasticCloudUser: true,
- });
- });
-
- test('user hash does not include cloudId when not provided', async () => {
- const { coreSetup } = await setupPlugin({
- config: {},
- currentUserProps: { username },
- });
-
- expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled();
-
- const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find(
- ([{ name }]) => name === 'cloud_user_id'
- )!;
-
- await expect(firstValueFrom(context$)).resolves.toEqual({
- userId: expectedHashedPlainUsername,
- isElasticCloudUser: false,
- });
- });
-
- test('user hash is undefined when failed to fetch a user', async () => {
- const { coreSetup } = await setupPlugin({
- currentUserProps: new Error('failed to fetch a user'),
- });
-
- expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled();
-
- const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find(
- ([{ name }]) => name === 'cloud_user_id'
- )!;
-
- await expect(firstValueFrom(context$)).resolves.toEqual({
- userId: undefined,
- isElasticCloudUser: false,
- });
- });
- });
-
- describe('setupChat', () => {
- let consoleMock: jest.SpyInstance;
-
- beforeEach(() => {
- consoleMock = jest.spyOn(console, 'debug').mockImplementation(() => {});
- });
-
- afterEach(() => {
- consoleMock.mockRestore();
- });
-
- const setupPlugin = async ({
- config = {},
- securityEnabled = true,
- currentUserProps = {},
- isCloudEnabled = true,
- failHttp = false,
- }: {
- config?: Partial;
- securityEnabled?: boolean;
- currentUserProps?: Record;
- isCloudEnabled?: boolean;
- failHttp?: boolean;
- }) => {
- const initContext = coreMock.createPluginInitializerContext({
- ...baseConfig,
- id: isCloudEnabled ? 'cloud-id' : null,
- ...config,
- });
-
- const plugin = new CloudPlugin(initContext);
-
- const coreSetup = coreMock.createSetup();
- const coreStart = coreMock.createStart();
-
- if (failHttp) {
- coreSetup.http.get.mockImplementation(() => {
- throw new Error('HTTP request failed');
- });
- }
-
- coreSetup.getStartServices.mockResolvedValue([coreStart, {}, undefined]);
-
- const securitySetup = securityMock.createSetup();
- securitySetup.authc.getCurrentUser.mockResolvedValue(
- securityMock.createMockAuthenticatedUser(currentUserProps)
- );
-
- const setup = plugin.setup(coreSetup, securityEnabled ? { security: securitySetup } : {});
-
- return { initContext, plugin, setup, coreSetup };
- };
-
- it('chatConfig is not retrieved if cloud is not enabled', async () => {
- const { coreSetup } = await setupPlugin({ isCloudEnabled: false });
- expect(coreSetup.http.get).not.toHaveBeenCalled();
- });
-
- it('chatConfig is not retrieved if security is not enabled', async () => {
- const { coreSetup } = await setupPlugin({ securityEnabled: false });
- expect(coreSetup.http.get).not.toHaveBeenCalled();
- });
-
- it('chatConfig is not retrieved if chat is enabled but url is not provided', async () => {
- // @ts-expect-error 2741
- const { coreSetup } = await setupPlugin({ config: { chat: { enabled: true } } });
- expect(coreSetup.http.get).not.toHaveBeenCalled();
- });
-
- it('chatConfig is not retrieved if internal API fails', async () => {
- const { coreSetup } = await setupPlugin({
- config: { chat: { enabled: true, chatURL: 'http://chat.elastic.co' } },
- failHttp: true,
- });
- expect(coreSetup.http.get).toHaveBeenCalled();
- expect(consoleMock).toHaveBeenCalled();
- });
-
- it('chatConfig is retrieved if chat is enabled and url is provided', async () => {
- const { coreSetup } = await setupPlugin({
- config: { chat: { enabled: true, chatURL: 'http://chat.elastic.co' } },
- });
- expect(coreSetup.http.get).toHaveBeenCalled();
- });
- });
-
describe('interface', () => {
const setupPlugin = () => {
const initContext = coreMock.createPluginInitializerContext({
@@ -317,7 +27,7 @@ describe('Cloud Plugin', () => {
const plugin = new CloudPlugin(initContext);
const coreSetup = coreMock.createSetup();
- const setup = plugin.setup(coreSetup, {});
+ const setup = plugin.setup(coreSetup);
return { setup };
};
@@ -361,49 +71,10 @@ describe('Cloud Plugin', () => {
const { setup } = setupPlugin();
expect(setup.cname).toBe('cloud.elastic.co');
});
- });
-
- describe('Set up cloudExperiments', () => {
- describe('when cloud ID is not provided in the config', () => {
- let cloudExperiments: jest.Mocked;
- beforeEach(() => {
- const plugin = new CloudPlugin(coreMock.createPluginInitializerContext(baseConfig));
- cloudExperiments = cloudExperimentsMock.createSetupMock();
- plugin.setup(coreMock.createSetup(), { cloudExperiments });
- });
- test('does not call cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser).not.toHaveBeenCalled();
- });
- });
-
- describe('when cloud ID is provided in the config', () => {
- let cloudExperiments: jest.Mocked;
- beforeEach(() => {
- const plugin = new CloudPlugin(
- coreMock.createPluginInitializerContext({ ...baseConfig, id: 'cloud test' })
- );
- cloudExperiments = cloudExperimentsMock.createSetupMock();
- plugin.setup(coreMock.createSetup(), { cloudExperiments });
- });
-
- test('calls cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser).toHaveBeenCalledTimes(1);
- });
-
- test('the cloud ID is hashed when calling cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser.mock.calls[0][0]).toEqual(
- '1acb4a1cc1c3d672a8d826055d897c2623ceb1d4fb07e46d97986751a36b06cf'
- );
- });
-
- test('specifies the Kibana version when calling cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser.mock.calls[0][1]).toEqual(
- expect.objectContaining({
- kibanaVersion: 'version',
- })
- );
- });
+ it('exposes registerCloudService', () => {
+ const { setup } = setupPlugin();
+ expect(setup.registerCloudService).toBeDefined();
});
});
});
@@ -426,9 +97,8 @@ describe('Cloud Plugin', () => {
})
);
const coreSetup = coreMock.createSetup();
- const homeSetup = homePluginMock.createSetupContract();
- plugin.setup(coreSetup, { home: homeSetup });
+ plugin.setup(coreSetup);
return { coreSetup, plugin };
};
@@ -437,8 +107,7 @@ describe('Cloud Plugin', () => {
const { plugin } = startPlugin();
const coreStart = coreMock.createStart();
- const securityStart = securityMock.createStart();
- plugin.start(coreStart, { security: securityStart });
+ plugin.start(coreStart);
expect(coreStart.chrome.setHelpSupportUrl).toHaveBeenCalledTimes(1);
expect(coreStart.chrome.setHelpSupportUrl.mock.calls[0]).toMatchInlineSnapshot(`
@@ -447,177 +116,5 @@ describe('Cloud Plugin', () => {
]
`);
});
-
- it('does not register custom nav links on anonymous pages', async () => {
- const { plugin } = startPlugin();
-
- const coreStart = coreMock.createStart();
- coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true);
-
- const securityStart = securityMock.createStart();
- securityStart.authc.getCurrentUser.mockResolvedValue(
- securityMock.createMockAuthenticatedUser({
- elastic_cloud_user: true,
- })
- );
-
- plugin.start(coreStart, { security: securityStart });
-
- await nextTick();
-
- expect(coreStart.chrome.setCustomNavLink).not.toHaveBeenCalled();
- expect(securityStart.authc.getCurrentUser).not.toHaveBeenCalled();
- });
-
- it('registers a custom nav link for cloud users', async () => {
- const { plugin } = startPlugin();
-
- const coreStart = coreMock.createStart();
- const securityStart = securityMock.createStart();
-
- securityStart.authc.getCurrentUser.mockResolvedValue(
- securityMock.createMockAuthenticatedUser({
- elastic_cloud_user: true,
- })
- );
- plugin.start(coreStart, { security: securityStart });
-
- await nextTick();
-
- expect(coreStart.chrome.setCustomNavLink).toHaveBeenCalledTimes(1);
- expect(coreStart.chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- Object {
- "euiIconType": "logoCloud",
- "href": "https://cloud.elastic.co/abc123",
- "title": "Manage this deployment",
- },
- ]
- `);
- });
-
- it('registers a custom nav link when there is an error retrieving the current user', async () => {
- const { plugin } = startPlugin();
-
- const coreStart = coreMock.createStart();
- const securityStart = securityMock.createStart();
- securityStart.authc.getCurrentUser.mockRejectedValue(new Error('something happened'));
- plugin.start(coreStart, { security: securityStart });
-
- await nextTick();
-
- expect(coreStart.chrome.setCustomNavLink).toHaveBeenCalledTimes(1);
- expect(coreStart.chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- Object {
- "euiIconType": "logoCloud",
- "href": "https://cloud.elastic.co/abc123",
- "title": "Manage this deployment",
- },
- ]
- `);
- });
-
- it('does not register a custom nav link for non-cloud users', async () => {
- const { plugin } = startPlugin();
-
- const coreStart = coreMock.createStart();
- const securityStart = securityMock.createStart();
- securityStart.authc.getCurrentUser.mockResolvedValue(
- securityMock.createMockAuthenticatedUser({
- elastic_cloud_user: false,
- })
- );
- plugin.start(coreStart, { security: securityStart });
-
- await nextTick();
-
- expect(coreStart.chrome.setCustomNavLink).not.toHaveBeenCalled();
- });
-
- it('registers user profile links for cloud users', async () => {
- const { plugin } = startPlugin();
-
- const coreStart = coreMock.createStart();
- const securityStart = securityMock.createStart();
- securityStart.authc.getCurrentUser.mockResolvedValue(
- securityMock.createMockAuthenticatedUser({
- elastic_cloud_user: true,
- })
- );
- plugin.start(coreStart, { security: securityStart });
-
- await nextTick();
-
- expect(securityStart.navControlService.addUserMenuLinks).toHaveBeenCalledTimes(1);
- expect(securityStart.navControlService.addUserMenuLinks.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- Array [
- Object {
- "href": "https://cloud.elastic.co/profile/alice",
- "iconType": "user",
- "label": "Edit profile",
- "order": 100,
- "setAsProfile": true,
- },
- Object {
- "href": "https://cloud.elastic.co/org/myOrg",
- "iconType": "gear",
- "label": "Account & Billing",
- "order": 200,
- },
- ],
- ]
- `);
- });
-
- it('registers profile links when there is an error retrieving the current user', async () => {
- const { plugin } = startPlugin();
-
- const coreStart = coreMock.createStart();
- const securityStart = securityMock.createStart();
- securityStart.authc.getCurrentUser.mockRejectedValue(new Error('something happened'));
- plugin.start(coreStart, { security: securityStart });
-
- await nextTick();
-
- expect(securityStart.navControlService.addUserMenuLinks).toHaveBeenCalledTimes(1);
- expect(securityStart.navControlService.addUserMenuLinks.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- Array [
- Object {
- "href": "https://cloud.elastic.co/profile/alice",
- "iconType": "user",
- "label": "Edit profile",
- "order": 100,
- "setAsProfile": true,
- },
- Object {
- "href": "https://cloud.elastic.co/org/myOrg",
- "iconType": "gear",
- "label": "Account & Billing",
- "order": 200,
- },
- ],
- ]
- `);
- });
-
- it('does not register profile links for non-cloud users', async () => {
- const { plugin } = startPlugin();
-
- const coreStart = coreMock.createStart();
- const securityStart = securityMock.createStart();
- securityStart.authc.getCurrentUser.mockResolvedValue(
- securityMock.createMockAuthenticatedUser({
- elastic_cloud_user: false,
- })
- );
- plugin.start(coreStart, { security: securityStart });
-
- await nextTick();
-
- expect(securityStart.navControlService.addUserMenuLinks).not.toHaveBeenCalled();
- });
});
});
diff --git a/x-pack/plugins/cloud/public/plugin.tsx b/x-pack/plugins/cloud/public/plugin.tsx
index c27668feb09bd..f50f41f3c79cd 100644
--- a/x-pack/plugins/cloud/public/plugin.tsx
+++ b/x-pack/plugins/cloud/public/plugin.tsx
@@ -6,34 +6,12 @@
*/
import React, { FC } from 'react';
-import type {
- CoreSetup,
- CoreStart,
- Plugin,
- PluginInitializerContext,
- HttpStart,
- IBasePath,
- AnalyticsServiceSetup,
-} from '@kbn/core/public';
-import { i18n } from '@kbn/i18n';
-import useObservable from 'react-use/lib/useObservable';
-import { BehaviorSubject, catchError, from, map, of } from 'rxjs';
+import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
-import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
-import { HomePublicPluginSetup } from '@kbn/home-plugin/public';
-import { Sha256 } from '@kbn/crypto-browser';
-import type { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common';
import { registerCloudDeploymentIdAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
-import {
- ELASTIC_SUPPORT_LINK,
- CLOUD_SNAPSHOTS_PATH,
- GET_CHAT_USER_DATA_ROUTE_PATH,
-} from '../common/constants';
-import type { GetChatUserDataResponseBody } from '../common/types';
-import { createUserMenuLinks } from './user_menu_links';
+import { ELASTIC_SUPPORT_LINK, CLOUD_SNAPSHOTS_PATH } from '../common/constants';
import { getFullCloudUrl } from './utils';
-import { ChatConfig, ServicesProvider } from './services';
export interface CloudConfigType {
id?: string;
@@ -47,23 +25,6 @@ export interface CloudConfigType {
org_id?: string;
eventTypesAllowlist?: string[];
};
- /** Configuration to enable live chat in Cloud-enabled instances of Kibana. */
- chat: {
- /** Determines if chat is enabled. */
- enabled: boolean;
- /** The URL to the remotely-hosted chat application. */
- chatURL: string;
- };
-}
-
-interface CloudSetupDependencies {
- home?: HomePublicPluginSetup;
- security?: Pick;
- cloudExperiments?: CloudExperimentsPluginSetup;
-}
-
-interface CloudStartDependencies {
- security?: SecurityPluginStart;
}
export interface CloudStart {
@@ -71,6 +32,26 @@ export interface CloudStart {
* A React component that provides a pre-wired `React.Context` which connects components to Cloud services.
*/
CloudContextProvider: FC<{}>;
+ /**
+ * `true` when Kibana is running on Elastic Cloud.
+ */
+ isCloudEnabled: boolean;
+ /**
+ * Cloud ID. Undefined if not running on Cloud.
+ */
+ cloudId?: string;
+ /**
+ * The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud.
+ */
+ deploymentUrl?: string;
+ /**
+ * The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud.
+ */
+ profileUrl?: string;
+ /**
+ * The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud.
+ */
+ organizationUrl?: string;
}
export interface CloudSetup {
@@ -82,268 +63,93 @@ export interface CloudSetup {
organizationUrl?: string;
snapshotsUrl?: string;
isCloudEnabled: boolean;
+ registerCloudService: (contextProvider: FC) => void;
}
-interface SetupFullStoryDeps {
- analytics: AnalyticsServiceSetup;
- basePath: IBasePath;
-}
-
-interface SetupChatDeps extends Pick {
- http: CoreSetup['http'];
+interface CloudUrls {
+ deploymentUrl?: string;
+ profileUrl?: string;
+ organizationUrl?: string;
+ snapshotsUrl?: string;
}
export class CloudPlugin implements Plugin {
private readonly config: CloudConfigType;
private readonly isCloudEnabled: boolean;
- private chatConfig$ = new BehaviorSubject({ enabled: false });
+ private readonly contextProviders: FC[] = [];
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get();
this.isCloudEnabled = getIsCloudEnabled(this.config.id);
}
- public setup(core: CoreSetup, { cloudExperiments, home, security }: CloudSetupDependencies) {
- this.setupTelemetryContext(core.analytics, security, this.config.id);
-
- this.setupFullStory({ analytics: core.analytics, basePath: core.http.basePath }).catch((e) =>
- // eslint-disable-next-line no-console
- console.debug(`Error setting up FullStory: ${e.toString()}`)
- );
+ public setup(core: CoreSetup): CloudSetup {
+ registerCloudDeploymentIdAnalyticsContext(core.analytics, this.config.id);
- const {
- id,
- cname,
- profile_url: profileUrl,
- organization_url: organizationUrl,
- deployment_url: deploymentUrl,
- base_url: baseUrl,
- } = this.config;
-
- if (this.isCloudEnabled && id) {
- // We use the Hashed Cloud Deployment ID as the userId in the Cloud Experiments
- cloudExperiments?.identifyUser(sha256(id), {
- kibanaVersion: this.initializerContext.env.packageInfo.version,
- });
- }
-
- this.setupChat({ http: core.http, security }).catch((e) =>
- // eslint-disable-next-line no-console
- console.debug(`Error setting up Chat: ${e.toString()}`)
- );
-
- if (home) {
- home.environment.update({ cloud: this.isCloudEnabled });
- if (this.isCloudEnabled) {
- home.tutorials.setVariable('cloud', { id, baseUrl, profileUrl, deploymentUrl });
- }
- }
-
- const fullCloudDeploymentUrl = getFullCloudUrl(baseUrl, deploymentUrl);
- const fullCloudProfileUrl = getFullCloudUrl(baseUrl, profileUrl);
- const fullCloudOrganizationUrl = getFullCloudUrl(baseUrl, organizationUrl);
- const fullCloudSnapshotsUrl = `${fullCloudDeploymentUrl}/${CLOUD_SNAPSHOTS_PATH}`;
+ const { id, cname, base_url: baseUrl } = this.config;
return {
cloudId: id,
cname,
baseUrl,
- deploymentUrl: fullCloudDeploymentUrl,
- profileUrl: fullCloudProfileUrl,
- organizationUrl: fullCloudOrganizationUrl,
- snapshotsUrl: fullCloudSnapshotsUrl,
+ ...this.getCloudUrls(),
isCloudEnabled: this.isCloudEnabled,
+ registerCloudService: (contextProvider) => {
+ this.contextProviders.push(contextProvider);
+ },
};
}
- public start(coreStart: CoreStart, { security }: CloudStartDependencies): CloudStart {
- const { deployment_url: deploymentUrl, base_url: baseUrl } = this.config;
+ public start(coreStart: CoreStart): CloudStart {
coreStart.chrome.setHelpSupportUrl(ELASTIC_SUPPORT_LINK);
- const setLinks = (authorized: boolean) => {
- if (!authorized) return;
-
- if (baseUrl && deploymentUrl) {
- coreStart.chrome.setCustomNavLink({
- title: i18n.translate('xpack.cloud.deploymentLinkLabel', {
- defaultMessage: 'Manage this deployment',
- }),
- euiIconType: 'logoCloud',
- href: getFullCloudUrl(baseUrl, deploymentUrl),
- });
- }
-
- if (security && this.isCloudEnabled) {
- const userMenuLinks = createUserMenuLinks(this.config);
- security.navControlService.addUserMenuLinks(userMenuLinks);
- }
- };
-
- this.checkIfAuthorizedForLinks({ http: coreStart.http, security })
- .then(setLinks)
- // In the event of an unexpected error, fail *open*.
- // Cloud admin console will always perform the actual authorization checks.
- .catch(() => setLinks(true));
-
- // There's a risk that the request for chat config will take too much time to complete, and the provider
- // will maintain a stale value. To avoid this, we'll use an Observable.
+ // Nest all the registered context providers under the Cloud Services Provider.
+ // This way, plugins only need to require Cloud's context provider to have all the enriched Cloud services.
const CloudContextProvider: FC = ({ children }) => {
- const chatConfig = useObservable(this.chatConfig$, { enabled: false });
- return {children};
+ return (
+ <>
+ {this.contextProviders.reduce(
+ (acc, ContextProvider) => (
+ {acc}
+ ),
+ children
+ )}
+ >
+ );
};
+ const { deploymentUrl, profileUrl, organizationUrl } = this.getCloudUrls();
+
return {
CloudContextProvider,
+ isCloudEnabled: this.isCloudEnabled,
+ cloudId: this.config.id,
+ deploymentUrl,
+ profileUrl,
+ organizationUrl,
};
}
public stop() {}
- /**
- * Determines if the current user should see links back to Cloud.
- * This isn't a true authorization check, but rather a heuristic to
- * see if the current user is *likely* a cloud deployment administrator.
- *
- * At this point, we do not have enough information to reliably make this determination,
- * but we do know that all cloud deployment admins are superusers by default.
- */
- private async checkIfAuthorizedForLinks({
- http,
- security,
- }: {
- http: HttpStart;
- security?: SecurityPluginStart;
- }) {
- if (http.anonymousPaths.isAnonymous(window.location.pathname)) {
- return false;
- }
- // Security plugin is disabled
- if (!security) return true;
-
- // Otherwise check if user is a cloud user.
- // If user is not defined due to an unexpected error, then fail *open*.
- // Cloud admin console will always perform the actual authorization checks.
- const user = await security.authc.getCurrentUser().catch(() => null);
- return user?.elastic_cloud_user ?? true;
- }
-
- /**
- * If the right config is provided, register the FullStory shipper to the analytics client.
- * @param analytics Core's Analytics service's setup contract.
- * @param basePath Core's http.basePath helper.
- * @private
- */
- private async setupFullStory({ analytics, basePath }: SetupFullStoryDeps) {
- const { enabled, org_id: fullStoryOrgId, eventTypesAllowlist } = this.config.full_story;
- if (!enabled || !fullStoryOrgId) {
- return; // do not load any FullStory code in the browser if not enabled
- }
-
- // Keep this import async so that we do not load any FullStory code into the browser when it is disabled.
- const { FullStoryShipper } = await import('@kbn/analytics-shippers-fullstory');
- analytics.registerShipper(FullStoryShipper, {
- eventTypesAllowlist,
- fullStoryOrgId,
- // Load an Elastic-internally audited script. Ideally, it should be hosted on a CDN.
- scriptUrl: basePath.prepend(
- `/internal/cloud/${this.initializerContext.env.packageInfo.buildNum}/fullstory.js`
- ),
- namespace: 'FSKibana',
- });
- }
-
- /**
- * Set up the Analytics context providers.
- * @param analytics Core's Analytics service. The Setup contract.
- * @param security The security plugin.
- * @param cloudId The Cloud Org ID.
- * @private
- */
- private setupTelemetryContext(
- analytics: AnalyticsServiceSetup,
- security?: Pick,
- cloudId?: string
- ) {
- registerCloudDeploymentIdAnalyticsContext(analytics, cloudId);
-
- if (security) {
- analytics.registerContextProvider({
- name: 'cloud_user_id',
- context$: from(security.authc.getCurrentUser()).pipe(
- map((user) => {
- if (user.elastic_cloud_user) {
- // If the user is managed by ESS, use the plain username as the user ID:
- // The username is expected to be unique for these users,
- // and it matches how users are identified in the Cloud UI, so it allows us to correlate them.
- return { userId: user.username, isElasticCloudUser: true };
- }
-
- return {
- // For the rest of the authentication providers, we want to add the cloud deployment ID to make it unique.
- // Especially in the case of Elasticsearch-backed authentication, where users are commonly repeated
- // across multiple deployments (i.e.: `elastic` superuser).
- userId: cloudId ? `${cloudId}:${user.username}` : user.username,
- isElasticCloudUser: false,
- };
- }),
- // The hashing here is to keep it at clear as possible in our source code that we do not send literal user IDs
- map(({ userId, isElasticCloudUser }) => ({ userId: sha256(userId), isElasticCloudUser })),
- catchError(() => of({ userId: undefined, isElasticCloudUser: false }))
- ),
- schema: {
- userId: {
- type: 'keyword',
- _meta: { description: 'The user id scoped as seen by Cloud (hashed)' },
- },
- isElasticCloudUser: {
- type: 'boolean',
- _meta: {
- description: '`true` if the user is managed by ESS.',
- },
- },
- },
- });
- }
- }
-
- private async setupChat({ http, security }: SetupChatDeps) {
- if (!this.isCloudEnabled) {
- return;
- }
-
- const { enabled, chatURL } = this.config.chat;
-
- if (!security || !enabled || !chatURL) {
- return;
- }
-
- try {
- const {
- email,
- id,
- token: jwt,
- } = await http.get(GET_CHAT_USER_DATA_ROUTE_PATH);
+ private getCloudUrls(): CloudUrls {
+ const {
+ profile_url: profileUrl,
+ organization_url: organizationUrl,
+ deployment_url: deploymentUrl,
+ base_url: baseUrl,
+ } = this.config;
- if (!email || !id || !jwt) {
- return;
- }
+ const fullCloudDeploymentUrl = getFullCloudUrl(baseUrl, deploymentUrl);
+ const fullCloudProfileUrl = getFullCloudUrl(baseUrl, profileUrl);
+ const fullCloudOrganizationUrl = getFullCloudUrl(baseUrl, organizationUrl);
+ const fullCloudSnapshotsUrl = `${fullCloudDeploymentUrl}/${CLOUD_SNAPSHOTS_PATH}`;
- this.chatConfig$.next({
- enabled,
- chatURL,
- user: {
- email,
- id,
- jwt,
- },
- });
- } catch (e) {
- // eslint-disable-next-line no-console
- console.debug(`[cloud.chat] Could not retrieve chat config: ${e.res.status} ${e.message}`, e);
- }
+ return {
+ deploymentUrl: fullCloudDeploymentUrl,
+ profileUrl: fullCloudProfileUrl,
+ organizationUrl: fullCloudOrganizationUrl,
+ snapshotsUrl: fullCloudSnapshotsUrl,
+ };
}
}
-
-function sha256(str: string) {
- return new Sha256().update(str, 'utf8').digest('hex');
-}
diff --git a/x-pack/plugins/cloud/server/config.ts b/x-pack/plugins/cloud/server/config.ts
index aebbc65e50f18..512542c756798 100644
--- a/x-pack/plugins/cloud/server/config.ts
+++ b/x-pack/plugins/cloud/server/config.ts
@@ -18,32 +18,11 @@ const apmConfigSchema = schema.object({
),
});
-const fullStoryConfigSchema = schema.object({
- enabled: schema.boolean({ defaultValue: false }),
- org_id: schema.conditional(
- schema.siblingRef('enabled'),
- true,
- schema.string({ minLength: 1 }),
- schema.maybe(schema.string())
- ),
- eventTypesAllowlist: schema.arrayOf(schema.string(), {
- defaultValue: ['Loaded Kibana'],
- }),
-});
-
-const chatConfigSchema = schema.object({
- enabled: schema.boolean({ defaultValue: false }),
- chatURL: schema.maybe(schema.string()),
-});
-
const configSchema = schema.object({
apm: schema.maybe(apmConfigSchema),
base_url: schema.maybe(schema.string()),
- chat: chatConfigSchema,
- chatIdentitySecret: schema.maybe(schema.string()),
cname: schema.maybe(schema.string()),
deployment_url: schema.maybe(schema.string()),
- full_story: fullStoryConfigSchema,
id: schema.maybe(schema.string()),
organization_url: schema.maybe(schema.string()),
profile_url: schema.maybe(schema.string()),
@@ -54,10 +33,8 @@ export type CloudConfigType = TypeOf;
export const config: PluginConfigDescriptor = {
exposeToBrowser: {
base_url: true,
- chat: true,
cname: true,
deployment_url: true,
- full_story: true,
id: true,
organization_url: true,
profile_url: true,
diff --git a/x-pack/plugins/cloud/server/mocks.ts b/x-pack/plugins/cloud/server/mocks.ts
new file mode 100644
index 0000000000000..557e64edf6cc1
--- /dev/null
+++ b/x-pack/plugins/cloud/server/mocks.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 { CloudSetup } from '.';
+
+function createSetupMock(): jest.Mocked {
+ return {
+ cloudId: 'mock-cloud-id',
+ instanceSizeMb: 1234,
+ deploymentId: 'deployment-id',
+ isCloudEnabled: true,
+ apm: {
+ url: undefined,
+ secretToken: undefined,
+ },
+ };
+}
+
+export const cloudMock = {
+ createSetup: createSetupMock,
+};
diff --git a/x-pack/plugins/cloud/server/plugin.test.ts b/x-pack/plugins/cloud/server/plugin.test.ts
index 05109a4c54816..55be923e98cf8 100644
--- a/x-pack/plugins/cloud/server/plugin.test.ts
+++ b/x-pack/plugins/cloud/server/plugin.test.ts
@@ -7,111 +7,54 @@
import { coreMock } from '@kbn/core/server/mocks';
import { CloudPlugin } from './plugin';
-import { config } from './config';
-import { securityMock } from '@kbn/security-plugin/server/mocks';
-import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks';
-import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks';
-import { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common';
+
+const baseConfig = {
+ base_url: 'https://cloud.elastic.co',
+ deployment_url: '/abc123',
+ profile_url: '/user/settings/',
+ organization_url: '/account/',
+};
describe('Cloud Plugin', () => {
describe('#setup', () => {
- describe('setupSecurity', () => {
- it('properly handles missing optional Security dependency if Cloud ID is NOT set.', async () => {
- const plugin = new CloudPlugin(
- coreMock.createPluginInitializerContext(config.schema.validate({}))
- );
+ describe('interface', () => {
+ const setupPlugin = () => {
+ const initContext = coreMock.createPluginInitializerContext({
+ ...baseConfig,
+ id: 'cloudId',
+ cname: 'cloud.elastic.co',
+ });
+ const plugin = new CloudPlugin(initContext);
- expect(() =>
- plugin.setup(coreMock.createSetup(), {
- usageCollection: usageCollectionPluginMock.createSetupContract(),
- })
- ).not.toThrow();
- });
+ const coreSetup = coreMock.createSetup();
+ const setup = plugin.setup(coreSetup, {});
- it('properly handles missing optional Security dependency if Cloud ID is set.', async () => {
- const plugin = new CloudPlugin(
- coreMock.createPluginInitializerContext(config.schema.validate({ id: 'my-cloud' }))
- );
+ return { setup };
+ };
- expect(() =>
- plugin.setup(coreMock.createSetup(), {
- usageCollection: usageCollectionPluginMock.createSetupContract(),
- })
- ).not.toThrow();
+ it('exposes isCloudEnabled', () => {
+ const { setup } = setupPlugin();
+ expect(setup.isCloudEnabled).toBe(true);
});
- it('does not notify Security plugin about Cloud environment if Cloud ID is NOT set.', async () => {
- const plugin = new CloudPlugin(
- coreMock.createPluginInitializerContext(config.schema.validate({}))
- );
-
- const securityDependencyMock = securityMock.createSetup();
- plugin.setup(coreMock.createSetup(), {
- security: securityDependencyMock,
- usageCollection: usageCollectionPluginMock.createSetupContract(),
- });
-
- expect(securityDependencyMock.setIsElasticCloudDeployment).not.toHaveBeenCalled();
+ it('exposes cloudId', () => {
+ const { setup } = setupPlugin();
+ expect(setup.cloudId).toBe('cloudId');
});
- it('properly notifies Security plugin about Cloud environment if Cloud ID is set.', async () => {
- const plugin = new CloudPlugin(
- coreMock.createPluginInitializerContext(config.schema.validate({ id: 'my-cloud' }))
- );
-
- const securityDependencyMock = securityMock.createSetup();
- plugin.setup(coreMock.createSetup(), {
- security: securityDependencyMock,
- usageCollection: usageCollectionPluginMock.createSetupContract(),
- });
-
- expect(securityDependencyMock.setIsElasticCloudDeployment).toHaveBeenCalledTimes(1);
+ it('exposes instanceSizeMb', () => {
+ const { setup } = setupPlugin();
+ expect(setup.instanceSizeMb).toBeUndefined();
});
- });
- describe('Set up cloudExperiments', () => {
- describe('when cloud ID is not provided in the config', () => {
- let cloudExperiments: jest.Mocked;
- beforeEach(() => {
- const plugin = new CloudPlugin(
- coreMock.createPluginInitializerContext(config.schema.validate({}))
- );
- cloudExperiments = cloudExperimentsMock.createSetupMock();
- plugin.setup(coreMock.createSetup(), { cloudExperiments });
- });
-
- test('does not call cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser).not.toHaveBeenCalled();
- });
+ it('exposes deploymentId', () => {
+ const { setup } = setupPlugin();
+ expect(setup.deploymentId).toBe('abc123');
});
- describe('when cloud ID is provided in the config', () => {
- let cloudExperiments: jest.Mocked;
- beforeEach(() => {
- const plugin = new CloudPlugin(
- coreMock.createPluginInitializerContext(config.schema.validate({ id: 'cloud test' }))
- );
- cloudExperiments = cloudExperimentsMock.createSetupMock();
- plugin.setup(coreMock.createSetup(), { cloudExperiments });
- });
-
- test('calls cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser).toHaveBeenCalledTimes(1);
- });
-
- test('the cloud ID is hashed when calling cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser.mock.calls[0][0]).toEqual(
- '1acb4a1cc1c3d672a8d826055d897c2623ceb1d4fb07e46d97986751a36b06cf'
- );
- });
-
- test('specifies the Kibana version when calling cloudExperiments.identifyUser', async () => {
- expect(cloudExperiments.identifyUser.mock.calls[0][1]).toEqual(
- expect.objectContaining({
- kibanaVersion: 'version',
- })
- );
- });
+ it('exposes apm', () => {
+ const { setup } = setupPlugin();
+ expect(setup.apm).toStrictEqual({ url: undefined, secretToken: undefined });
});
});
});
diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts
index d38a57a4d3bab..9cf1a308800a0 100644
--- a/x-pack/plugins/cloud/server/plugin.ts
+++ b/x-pack/plugins/cloud/server/plugin.ts
@@ -5,24 +5,17 @@
* 2.0.
*/
-import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
-import { CoreSetup, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server';
-import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
-import type { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common';
-import { createSHA256Hash } from '@kbn/crypto';
+import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server';
+import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { registerCloudDeploymentIdAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context';
-import { CloudConfigType } from './config';
+import type { CloudConfigType } from './config';
import { registerCloudUsageCollector } from './collectors';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
import { parseDeploymentIdFromDeploymentUrl } from './utils';
-import { registerFullstoryRoute } from './routes/fullstory';
-import { registerChatRoute } from './routes/chat';
import { readInstanceSizeMb } from './env';
interface PluginsSetup {
usageCollection?: UsageCollectionSetup;
- security?: SecurityPluginSetup;
- cloudExperiments?: CloudExperimentsPluginSetup;
}
export interface CloudSetup {
@@ -37,52 +30,17 @@ export interface CloudSetup {
}
export class CloudPlugin implements Plugin {
- private readonly logger: Logger;
private readonly config: CloudConfigType;
- private readonly isDev: boolean;
constructor(private readonly context: PluginInitializerContext) {
- this.logger = this.context.logger.get();
this.config = this.context.config.get();
- this.isDev = this.context.env.mode.dev;
}
- public setup(
- core: CoreSetup,
- { cloudExperiments, usageCollection, security }: PluginsSetup
- ): CloudSetup {
- this.logger.debug('Setting up Cloud plugin');
+ public setup(core: CoreSetup, { usageCollection }: PluginsSetup): CloudSetup {
const isCloudEnabled = getIsCloudEnabled(this.config.id);
registerCloudDeploymentIdAnalyticsContext(core.analytics, this.config.id);
registerCloudUsageCollector(usageCollection, { isCloudEnabled });
- if (isCloudEnabled) {
- security?.setIsElasticCloudDeployment();
- }
-
- if (isCloudEnabled && this.config.id) {
- // We use the Cloud ID as the userId in the Cloud Experiments
- cloudExperiments?.identifyUser(createSHA256Hash(this.config.id), {
- kibanaVersion: this.context.env.packageInfo.version,
- });
- }
-
- if (this.config.full_story.enabled) {
- registerFullstoryRoute({
- httpResources: core.http.resources,
- packageInfo: this.context.env.packageInfo,
- });
- }
-
- if (this.config.chat.enabled && this.config.chatIdentitySecret) {
- registerChatRoute({
- router: core.http.createRouter(),
- chatIdentitySecret: this.config.chatIdentitySecret,
- security,
- isDev: this.isDev,
- });
- }
-
return {
cloudId: this.config.id,
instanceSizeMb: readInstanceSizeMb(),
diff --git a/x-pack/plugins/cloud/tsconfig.json b/x-pack/plugins/cloud/tsconfig.json
index d8c8a5c8eca44..ca9ba32ed10b0 100644
--- a/x-pack/plugins/cloud/tsconfig.json
+++ b/x-pack/plugins/cloud/tsconfig.json
@@ -16,8 +16,5 @@
"references": [
{ "path": "../../../src/core/tsconfig.json" },
{ "path": "../../../src/plugins/usage_collection/tsconfig.json" },
- { "path": "../../../src/plugins/home/tsconfig.json" },
- { "path": "../cloud_integrations/cloud_experiments/tsconfig.json" },
- { "path": "../security/tsconfig.json" },
]
}
diff --git a/x-pack/plugins/cloud/.storybook/decorator.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/decorator.tsx
similarity index 89%
rename from x-pack/plugins/cloud/.storybook/decorator.tsx
rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/decorator.tsx
index 4489b58f75759..3af8d04a598eb 100644
--- a/x-pack/plugins/cloud/.storybook/decorator.tsx
+++ b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/decorator.tsx
@@ -7,12 +7,11 @@
import React from 'react';
import { DecoratorFn } from '@storybook/react';
-import { ServicesProvider, CloudServices } from '../public/services';
+import { ServicesProvider, CloudChatServices } from '../public/services';
// TODO: move to a storybook implementation of the service using parameters.
-const services: CloudServices = {
+const services: CloudChatServices = {
chat: {
- enabled: true,
chatURL: 'https://elasticcloud-production-chat-us-east-1.s3.amazonaws.com/drift-iframe.html',
user: {
id: 'user-id',
diff --git a/x-pack/plugins/cloud/.storybook/index.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/index.ts
similarity index 100%
rename from x-pack/plugins/cloud/.storybook/index.ts
rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/index.ts
diff --git a/x-pack/plugins/cloud/.storybook/main.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/main.ts
similarity index 100%
rename from x-pack/plugins/cloud/.storybook/main.ts
rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/main.ts
diff --git a/x-pack/plugins/cloud/.storybook/manager.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/manager.ts
similarity index 100%
rename from x-pack/plugins/cloud/.storybook/manager.ts
rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/manager.ts
diff --git a/x-pack/plugins/cloud/.storybook/preview.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/preview.ts
similarity index 100%
rename from x-pack/plugins/cloud/.storybook/preview.ts
rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/preview.ts
diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/README.md b/x-pack/plugins/cloud_integrations/cloud_chat/README.md
new file mode 100755
index 0000000000000..cee3d9f5a6671
--- /dev/null
+++ b/x-pack/plugins/cloud_integrations/cloud_chat/README.md
@@ -0,0 +1,3 @@
+# Cloud Chat
+
+Integrates with DriftChat in order to provide live support to our Elastic Cloud users. This plugin should only run on Elastic Cloud.
diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts b/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts
new file mode 100755
index 0000000000000..d7bd133e5b4f9
--- /dev/null
+++ b/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const GET_CHAT_USER_DATA_ROUTE_PATH = '/internal/cloud/chat_user';
diff --git a/x-pack/plugins/cloud/common/types.ts b/x-pack/plugins/cloud_integrations/cloud_chat/common/types.ts
similarity index 100%
rename from x-pack/plugins/cloud/common/types.ts
rename to x-pack/plugins/cloud_integrations/cloud_chat/common/types.ts
diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js b/x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js
new file mode 100644
index 0000000000000..44f6f241d44d0
--- /dev/null
+++ b/x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js
@@ -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.
+ */
+
+module.exports = {
+ preset: '@kbn/test',
+ rootDir: '../../../../',
+ roots: ['/x-pack/plugins/cloud_integrations/cloud_chat'],
+ coverageDirectory:
+ '/target/kibana-coverage/jest/x-pack/plugins/cloud_integrations/cloud_chat',
+ coverageReporters: ['text', 'html'],
+ collectCoverageFrom: [
+ '/x-pack/plugins/cloud_integrations/cloud_chat/{common,public,server}/**/*.{ts,tsx}',
+ ],
+};
diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/kibana.json b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.json
new file mode 100755
index 0000000000000..76f7e34e71e56
--- /dev/null
+++ b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.json
@@ -0,0 +1,15 @@
+{
+ "id": "cloudChat",
+ "version": "1.0.0",
+ "kibanaVersion": "kibana",
+ "owner": {
+ "name": "Kibana Core",
+ "githubTeam": "kibana-core"
+ },
+ "description": "Chat available on Elastic Cloud deployments for quicker assistance.",
+ "server": true,
+ "ui": true,
+ "configPath": ["xpack", "cloud_integrations", "chat"],
+ "requiredPlugins": ["cloud"],
+ "optionalPlugins": ["security"]
+}
diff --git a/x-pack/plugins/cloud/public/components/chat/chat.stories.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.stories.tsx
similarity index 99%
rename from x-pack/plugins/cloud/public/components/chat/chat.stories.tsx
rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.stories.tsx
index 7e673e341cec7..295750ee43039 100644
--- a/x-pack/plugins/cloud/public/components/chat/chat.stories.tsx
+++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.stories.tsx
@@ -68,7 +68,6 @@ export const Component = ({ id, email, chatURL, jwt }: Params) => {
return (
{}, onReady, onResize }: Props) => {
}}
size="xs"
>
- {i18n.translate('xpack.cloud.chat.hideChatButtonLabel', {
+ {i18n.translate('xpack.cloudChat.hideChatButtonLabel', {
defaultMessage: 'Hide chat',
})}
@@ -80,7 +80,7 @@ export const Chat = ({ onHide = () => {}, onReady, onResize }: Props) => {
{button}
-
+
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.docsLink',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx
index 084fb4244cb7a..66ffbe45c1777 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx
@@ -14,6 +14,8 @@ import { i18n } from '@kbn/i18n';
import noMlModelsGraphicDark from '../../../../../../assets/images/no_ml_models_dark.svg';
import noMlModelsGraphicLight from '../../../../../../assets/images/no_ml_models_light.svg';
+import { docLinks } from '../../../../../shared/doc_links';
+
export const NoModelsPanel: React.FC = () => {
const { colorMode } = useEuiTheme();
@@ -43,8 +45,7 @@ export const NoModelsPanel: React.FC = () => {
>
}
footer={
- // TODO: insert correct docsLink here
-
+
{i18n.translate(
'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.docsLink',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx
index 07be63b54f3b5..9cab24190a2de 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx
@@ -87,7 +87,7 @@ export const SearchIndexPipelines: React.FC = () => {
+
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.docLink',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
index 17ee2230b2fb7..975e7981829f2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts
@@ -65,6 +65,7 @@ class DocLinks {
public crawlerGettingStarted: string;
public crawlerManaging: string;
public crawlerOverview: string;
+ public deployTrainedModels: string;
public documentLevelSecurity: string;
public elasticsearchCreateIndex: string;
public elasticsearchGettingStarted: string;
@@ -178,6 +179,7 @@ class DocLinks {
this.crawlerGettingStarted = '';
this.crawlerManaging = '';
this.crawlerOverview = '';
+ this.deployTrainedModels = '';
this.documentLevelSecurity = '';
this.elasticsearchCreateIndex = '';
this.elasticsearchGettingStarted = '';
@@ -293,6 +295,7 @@ class DocLinks {
this.crawlerGettingStarted = docLinks.links.enterpriseSearch.crawlerGettingStarted;
this.crawlerManaging = docLinks.links.enterpriseSearch.crawlerManaging;
this.crawlerOverview = docLinks.links.enterpriseSearch.crawlerOverview;
+ this.deployTrainedModels = docLinks.links.enterpriseSearch.deployTrainedModels;
this.documentLevelSecurity = docLinks.links.enterpriseSearch.documentLevelSecurity;
this.elasticsearchCreateIndex = docLinks.links.elasticsearch.createIndex;
this.elasticsearchGettingStarted = docLinks.links.elasticsearch.gettingStarted;
From 4753d7c170ea47e3bc178c2b48fd6507a150594d Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Tue, 4 Oct 2022 13:50:55 +0200
Subject: [PATCH 07/39] [ML] Explain Log Rate Spikes: Fix error handling.
(#142047)
- Fixes error handling that before was not providing enough information for debugging purposes and support. This will now output more fine grained error information to the Kibana server log. The analysis is now more resilient to errors for individual queries. For example, we don't stop the analysis anymore if individual queries for p-values or histograms fail.
- Moves the error callout above all other possible elements like empty prompts when the analysis doesn't return results.
---
.../api/explain_log_rate_spikes/actions.ts | 10 +
.../api/explain_log_rate_spikes/index.ts | 1 +
.../explain_log_rate_spikes_analysis.tsx | 54 +-
.../server/routes/explain_log_rate_spikes.ts | 556 +++++++++++-------
.../queries/fetch_change_point_p_values.ts | 16 +-
.../routes/queries/fetch_frequent_items.ts | 23 +-
.../apis/aiops/explain_log_rate_spikes.ts | 4 +-
7 files changed, 405 insertions(+), 259 deletions(-)
diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts
index e050946a489be..7c4e3a47f8b79 100644
--- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts
+++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts
@@ -18,6 +18,7 @@ export const API_ACTION_NAME = {
ADD_CHANGE_POINTS_GROUP: 'add_change_point_group',
ADD_CHANGE_POINTS_GROUP_HISTOGRAM: 'add_change_point_group_histogram',
ADD_ERROR: 'add_error',
+ PING: 'ping',
RESET: 'reset',
UPDATE_LOADING_STATE: 'update_loading_state',
} as const;
@@ -89,6 +90,14 @@ export function addErrorAction(payload: ApiActionAddError['payload']): ApiAction
};
}
+interface ApiActionPing {
+ type: typeof API_ACTION_NAME.PING;
+}
+
+export function pingAction(): ApiActionPing {
+ return { type: API_ACTION_NAME.PING };
+}
+
interface ApiActionReset {
type: typeof API_ACTION_NAME.RESET;
}
@@ -121,5 +130,6 @@ export type AiopsExplainLogRateSpikesApiAction =
| ApiActionAddChangePointsHistogram
| ApiActionAddChangePointsGroupHistogram
| ApiActionAddError
+ | ApiActionPing
| ApiActionReset
| ApiActionUpdateLoadingState;
diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts
index 5628b509980ad..c092b34c8b2b6 100644
--- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts
+++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts
@@ -11,6 +11,7 @@ export {
addChangePointsGroupHistogramAction,
addChangePointsHistogramAction,
addErrorAction,
+ pingAction,
resetAction,
updateLoadingStateAction,
API_ACTION_NAME,
diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx
index 2425161615915..9949ec537b77a 100644
--- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx
+++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx
@@ -172,6 +172,33 @@ export const ExplainLogRateSpikesAnalysis: FC
onCancel={cancel}
shouldRerunAnalysis={shouldRerunAnalysis}
/>
+ {errors.length > 0 ? (
+ <>
+
+
+ {errors.length === 1 ? (
+ {errors[0]}
+ ) : (
+
+ {errors.map((e, i) => (
+ - {e}
+ ))}
+
+ )}
+
+
+
+ >
+ ) : null}
{showSpikeAnalysisTable && foundGroups && (
}
/>
)}
- {errors.length > 0 && (
- <>
-
-
- {errors.length === 1 ? (
- {errors[0]}
- ) : (
-
- {errors.map((e, i) => (
- - {e}
- ))}
-
- )}
-
-
-
- >
- )}
{showSpikeAnalysisTable && groupResults && foundGroups ? (
{
+ logInfoMessage('aborted$ subscription trigger.');
shouldStop = true;
controller.abort();
});
request.events.completed$.subscribe(() => {
+ logInfoMessage('completed$ subscription trigger.');
shouldStop = true;
controller.abort();
});
- const { end, push, responseWithHeaders } = streamFactory(
- request.headers,
- logger,
- true
- );
+ const {
+ end: streamEnd,
+ push,
+ responseWithHeaders,
+ } = streamFactory(request.headers, logger, true);
+
+ function pushPing() {
+ push(pingAction());
+ }
+
+ const pingInterval = setInterval(pushPing, 1000);
+
+ function end() {
+ logInfoMessage('Ending analysis.');
+ clearInterval(pingInterval);
+ streamEnd();
+ }
function endWithUpdatedLoadingState() {
push(
@@ -114,9 +138,16 @@ export const defineExplainLogRateSpikesRoute = (
end();
}
+ function pushError(m: string) {
+ logInfoMessage('Push error.');
+ push(addErrorAction(m));
+ }
+
// Async IIFE to run the analysis while not blocking returning `responseWithHeaders`.
(async () => {
+ logInfoMessage('Reset.');
push(resetAction());
+ logInfoMessage('Load field candidates.');
push(
updateLoadingStateAction({
ccsWarning: false,
@@ -134,7 +165,8 @@ export const defineExplainLogRateSpikesRoute = (
try {
fieldCandidates = await fetchFieldCandidates(client, request.body);
} catch (e) {
- push(addErrorAction(e.toString()));
+ logger.error(`Failed to fetch field candidates, got: \n${e.toString()}`);
+ pushError(`Failed to fetch field candidates.`);
end();
return;
}
@@ -168,17 +200,33 @@ export const defineExplainLogRateSpikesRoute = (
const changePoints: ChangePoint[] = [];
const fieldsToSample = new Set();
const chunkSize = 10;
+ let chunkCount = 0;
const fieldCandidatesChunks = chunk(fieldCandidates, chunkSize);
+ logInfoMessage('Fetch p-values.');
+
for (const fieldCandidatesChunk of fieldCandidatesChunks) {
+ chunkCount++;
+ logInfoMessage(`Fetch p-values. Chunk ${chunkCount} of ${fieldCandidatesChunks.length}`);
let pValues: Awaited>;
try {
- pValues = await fetchChangePointPValues(client, request.body, fieldCandidatesChunk);
+ pValues = await fetchChangePointPValues(
+ client,
+ request.body,
+ fieldCandidatesChunk,
+ logger,
+ pushError
+ );
} catch (e) {
- push(addErrorAction(e.toString()));
- end();
- return;
+ logger.error(
+ `Failed to fetch p-values for ${JSON.stringify(
+ fieldCandidatesChunk
+ )}, got: \n${e.toString()}`
+ );
+ pushError(`Failed to fetch p-values for ${JSON.stringify(fieldCandidatesChunk)}.`);
+ // Still continue the analysis even if chunks of p-value queries fail.
+ continue;
}
if (pValues.length > 0) {
@@ -210,12 +258,15 @@ export const defineExplainLogRateSpikesRoute = (
);
if (shouldStop) {
+ logInfoMessage('shouldStop fetching p-values.');
+
end();
return;
}
}
if (changePoints?.length === 0) {
+ logInfoMessage('Stopping analysis, did not find change points.');
endWithUpdatedLoadingState();
return;
}
@@ -224,16 +275,27 @@ export const defineExplainLogRateSpikesRoute = (
{ fieldName: request.body.timeFieldName, type: KBN_FIELD_TYPES.DATE },
];
- const [overallTimeSeries] = (await fetchHistogramsForFields(
- client,
- request.body.index,
- { match_all: {} },
- // fields
- histogramFields,
- // samplerShardSize
- -1,
- undefined
- )) as [NumericChartData];
+ logInfoMessage('Fetch overall histogram.');
+
+ let overallTimeSeries: NumericChartData | undefined;
+ try {
+ overallTimeSeries = (
+ (await fetchHistogramsForFields(
+ client,
+ request.body.index,
+ { match_all: {} },
+ // fields
+ histogramFields,
+ // samplerShardSize
+ -1,
+ undefined
+ )) as [NumericChartData]
+ )[0];
+ } catch (e) {
+ logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`);
+ pushError(`Failed to fetch overall histogram data.`);
+ // Still continue the analysis even if loading the overall histogram fails.
+ }
function pushHistogramDataLoadingState() {
push(
@@ -251,6 +313,8 @@ export const defineExplainLogRateSpikesRoute = (
}
if (groupingEnabled) {
+ logInfoMessage('Group results.');
+
push(
updateLoadingStateAction({
ccsWarning: false,
@@ -283,208 +347,242 @@ export const defineExplainLogRateSpikesRoute = (
(g) => g.group.length > 1
);
- const { fields, df } = await fetchFrequentItems(
- client,
- request.body.index,
- JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer,
- deduplicatedChangePoints,
- request.body.timeFieldName,
- request.body.deviationMin,
- request.body.deviationMax
- );
-
- // The way the `frequent_items` aggregations works could return item sets that include
- // field/value pairs that are not part of the original list of significant change points.
- // This cleans up groups and removes those unrelated field/value pairs.
- const filteredDf = df
- .map((fi) => {
- fi.set = Object.entries(fi.set).reduce(
- (set, [field, value]) => {
- if (
- changePoints.some((cp) => cp.fieldName === field && cp.fieldValue === value)
- ) {
- set[field] = value;
+ try {
+ const { fields, df } = await fetchFrequentItems(
+ client,
+ request.body.index,
+ JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer,
+ deduplicatedChangePoints,
+ request.body.timeFieldName,
+ request.body.deviationMin,
+ request.body.deviationMax,
+ logger,
+ pushError
+ );
+
+ if (fields.length > 0 && df.length > 0) {
+ // The way the `frequent_items` aggregations works could return item sets that include
+ // field/value pairs that are not part of the original list of significant change points.
+ // This cleans up groups and removes those unrelated field/value pairs.
+ const filteredDf = df
+ .map((fi) => {
+ fi.set = Object.entries(fi.set).reduce(
+ (set, [field, value]) => {
+ if (
+ changePoints.some((cp) => cp.fieldName === field && cp.fieldValue === value)
+ ) {
+ set[field] = value;
+ }
+ return set;
+ },
+ {}
+ );
+ fi.size = Object.keys(fi.set).length;
+ return fi;
+ })
+ .filter((fi) => fi.size > 1);
+
+ // `frequent_items` returns lot of different small groups of field/value pairs that co-occur.
+ // The following steps analyse these small groups, identify overlap between these groups,
+ // and then summarize them in larger groups where possible.
+
+ // Get a tree structure based on `frequent_items`.
+ const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields);
+
+ // Each leave of the tree will be a summarized group of co-occuring field/value pairs.
+ const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []);
+
+ // To be able to display a more cleaned up results table in the UI, we identify field/value pairs
+ // that occur in multiple groups. This will allow us to highlight field/value pairs that are
+ // unique to a group in a better way. This step will also re-add duplicates we identified in the
+ // beginning and didn't pass on to the `frequent_items` agg.
+ const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves);
+ const changePointGroups = markDuplicates(treeLeaves, fieldValuePairCounts).map(
+ (g) => {
+ const group = [...g.group];
+
+ for (const groupItem of g.group) {
+ const { duplicate } = groupItem;
+ const duplicates = groupedChangePoints.find((d) =>
+ d.group.some(
+ (dg) =>
+ dg.fieldName === groupItem.fieldName &&
+ dg.fieldValue === groupItem.fieldValue
+ )
+ );
+
+ if (duplicates !== undefined) {
+ group.push(
+ ...duplicates.group.map((d) => {
+ return {
+ fieldName: d.fieldName,
+ fieldValue: d.fieldValue,
+ duplicate,
+ };
+ })
+ );
+ }
}
- return set;
- },
- {}
- );
- fi.size = Object.keys(fi.set).length;
- return fi;
- })
- .filter((fi) => fi.size > 1);
-
- // `frequent_items` returns lot of different small groups of field/value pairs that co-occur.
- // The following steps analyse these small groups, identify overlap between these groups,
- // and then summarize them in larger groups where possible.
-
- // Get a tree structure based on `frequent_items`.
- const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields);
-
- // Each leave of the tree will be a summarized group of co-occuring field/value pairs.
- const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []);
-
- // To be able to display a more cleaned up results table in the UI, we identify field/value pairs
- // that occur in multiple groups. This will allow us to highlight field/value pairs that are
- // unique to a group in a better way. This step will also re-add duplicates we identified in the
- // beginning and didn't pass on to the `frequent_items` agg.
- const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves);
- const changePointGroups = markDuplicates(treeLeaves, fieldValuePairCounts).map((g) => {
- const group = [...g.group];
-
- for (const groupItem of g.group) {
- const { duplicate } = groupItem;
- const duplicates = groupedChangePoints.find((d) =>
- d.group.some(
- (dg) =>
- dg.fieldName === groupItem.fieldName && dg.fieldValue === groupItem.fieldValue
- )
- );
-
- if (duplicates !== undefined) {
- group.push(
- ...duplicates.group.map((d) => {
- return {
- fieldName: d.fieldName,
- fieldValue: d.fieldValue,
- duplicate,
- };
- })
- );
- }
- }
- return {
- ...g,
- group,
- };
- });
-
- // Some field/value pairs might not be part of the `frequent_items` result set, for example
- // because they don't co-occur with other field/value pairs or because of the limits we set on the query.
- // In this next part we identify those missing pairs and add them as individual groups.
- const missingChangePoints = deduplicatedChangePoints.filter((cp) => {
- return !changePointGroups.some((cpg) => {
- return cpg.group.some(
- (d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue
+ return {
+ ...g,
+ group,
+ };
+ }
);
- });
- });
- changePointGroups.push(
- ...missingChangePoints.map(({ fieldName, fieldValue, doc_count: docCount, pValue }) => {
- const duplicates = groupedChangePoints.find((d) =>
- d.group.some((dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue)
+ // Some field/value pairs might not be part of the `frequent_items` result set, for example
+ // because they don't co-occur with other field/value pairs or because of the limits we set on the query.
+ // In this next part we identify those missing pairs and add them as individual groups.
+ const missingChangePoints = deduplicatedChangePoints.filter((cp) => {
+ return !changePointGroups.some((cpg) => {
+ return cpg.group.some(
+ (d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue
+ );
+ });
+ });
+
+ changePointGroups.push(
+ ...missingChangePoints.map(
+ ({ fieldName, fieldValue, doc_count: docCount, pValue }) => {
+ const duplicates = groupedChangePoints.find((d) =>
+ d.group.some(
+ (dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue
+ )
+ );
+ if (duplicates !== undefined) {
+ return {
+ id: `${stringHash(
+ JSON.stringify(
+ duplicates.group.map((d) => ({
+ fieldName: d.fieldName,
+ fieldValue: d.fieldValue,
+ }))
+ )
+ )}`,
+ group: duplicates.group.map((d) => ({
+ fieldName: d.fieldName,
+ fieldValue: d.fieldValue,
+ duplicate: false,
+ })),
+ docCount,
+ pValue,
+ };
+ } else {
+ return {
+ id: `${stringHash(JSON.stringify({ fieldName, fieldValue }))}`,
+ group: [
+ {
+ fieldName,
+ fieldValue,
+ duplicate: false,
+ },
+ ],
+ docCount,
+ pValue,
+ };
+ }
+ }
+ )
);
- if (duplicates !== undefined) {
- return {
- id: `${stringHash(
- JSON.stringify(
- duplicates.group.map((d) => ({
- fieldName: d.fieldName,
- fieldValue: d.fieldValue,
- }))
- )
- )}`,
- group: duplicates.group.map((d) => ({
- fieldName: d.fieldName,
- fieldValue: d.fieldValue,
- duplicate: false,
- })),
- docCount,
- pValue,
- };
- } else {
- return {
- id: `${stringHash(JSON.stringify({ fieldName, fieldValue }))}`,
- group: [
- {
- fieldName,
- fieldValue,
- duplicate: false,
- },
- ],
- docCount,
- pValue,
- };
- }
- })
- );
-
- // Finally, we'll find out if there's at least one group with at least two items,
- // only then will we return the groups to the clients and make the grouping option available.
- const maxItems = Math.max(...changePointGroups.map((g) => g.group.length));
- if (maxItems > 1) {
- push(addChangePointsGroupAction(changePointGroups));
- }
+ // Finally, we'll find out if there's at least one group with at least two items,
+ // only then will we return the groups to the clients and make the grouping option available.
+ const maxItems = Math.max(...changePointGroups.map((g) => g.group.length));
- loaded += PROGRESS_STEP_GROUPING;
+ if (maxItems > 1) {
+ push(addChangePointsGroupAction(changePointGroups));
+ }
- pushHistogramDataLoadingState();
+ loaded += PROGRESS_STEP_GROUPING;
- if (changePointGroups) {
- await asyncForEach(changePointGroups, async (cpg, index) => {
- const histogramQuery = {
- bool: {
- filter: cpg.group.map((d) => ({
- term: { [d.fieldName]: d.fieldValue },
- })),
- },
- };
+ pushHistogramDataLoadingState();
- const [cpgTimeSeries] = (await fetchHistogramsForFields(
- client,
- request.body.index,
- histogramQuery,
- // fields
- [
- {
- fieldName: request.body.timeFieldName,
- type: KBN_FIELD_TYPES.DATE,
- interval: overallTimeSeries.interval,
- min: overallTimeSeries.stats[0],
- max: overallTimeSeries.stats[1],
- },
- ],
- // samplerShardSize
- -1,
- undefined
- )) as [NumericChartData];
+ logInfoMessage('Fetch group histograms.');
- const histogram =
- overallTimeSeries.data.map((o, i) => {
- const current = cpgTimeSeries.data.find(
- (d1) => d1.key_as_string === o.key_as_string
- ) ?? {
- doc_count: 0,
- };
- return {
- key: o.key,
- key_as_string: o.key_as_string ?? '',
- doc_count_change_point: current.doc_count,
- doc_count_overall: Math.max(0, o.doc_count - current.doc_count),
+ await asyncForEach(changePointGroups, async (cpg) => {
+ if (overallTimeSeries !== undefined) {
+ const histogramQuery = {
+ bool: {
+ filter: cpg.group.map((d) => ({
+ term: { [d.fieldName]: d.fieldValue },
+ })),
+ },
};
- }) ?? [];
- push(
- addChangePointsGroupHistogramAction([
- {
- id: cpg.id,
- histogram,
- },
- ])
- );
- });
+ let cpgTimeSeries: NumericChartData;
+ try {
+ cpgTimeSeries = (
+ (await fetchHistogramsForFields(
+ client,
+ request.body.index,
+ histogramQuery,
+ // fields
+ [
+ {
+ fieldName: request.body.timeFieldName,
+ type: KBN_FIELD_TYPES.DATE,
+ interval: overallTimeSeries.interval,
+ min: overallTimeSeries.stats[0],
+ max: overallTimeSeries.stats[1],
+ },
+ ],
+ // samplerShardSize
+ -1,
+ undefined
+ )) as [NumericChartData]
+ )[0];
+ } catch (e) {
+ logger.error(
+ `Failed to fetch the histogram data for group #${
+ cpg.id
+ }, got: \n${e.toString()}`
+ );
+ pushError(`Failed to fetch the histogram data for group #${cpg.id}.`);
+ return;
+ }
+ const histogram =
+ overallTimeSeries.data.map((o, i) => {
+ const current = cpgTimeSeries.data.find(
+ (d1) => d1.key_as_string === o.key_as_string
+ ) ?? {
+ doc_count: 0,
+ };
+ return {
+ key: o.key,
+ key_as_string: o.key_as_string ?? '',
+ doc_count_change_point: current.doc_count,
+ doc_count_overall: Math.max(0, o.doc_count - current.doc_count),
+ };
+ }) ?? [];
+
+ push(
+ addChangePointsGroupHistogramAction([
+ {
+ id: cpg.id,
+ histogram,
+ },
+ ])
+ );
+ }
+ });
+ }
+ } catch (e) {
+ logger.error(
+ `Failed to transform field/value pairs into groups, got: \n${e.toString()}`
+ );
+ pushError(`Failed to transform field/value pairs into groups.`);
}
}
loaded += PROGRESS_STEP_HISTOGRAMS_GROUPS;
+ logInfoMessage('Fetch field/value histograms.');
+
// time series filtered by fields
- if (changePoints) {
- await asyncForEach(changePoints, async (cp, index) => {
- if (changePoints) {
+ if (changePoints && overallTimeSeries !== undefined) {
+ await asyncForEach(changePoints, async (cp) => {
+ if (overallTimeSeries !== undefined) {
const histogramQuery = {
bool: {
filter: [
@@ -495,24 +593,40 @@ export const defineExplainLogRateSpikesRoute = (
},
};
- const [cpTimeSeries] = (await fetchHistogramsForFields(
- client,
- request.body.index,
- histogramQuery,
- // fields
- [
- {
- fieldName: request.body.timeFieldName,
- type: KBN_FIELD_TYPES.DATE,
- interval: overallTimeSeries.interval,
- min: overallTimeSeries.stats[0],
- max: overallTimeSeries.stats[1],
- },
- ],
- // samplerShardSize
- -1,
- undefined
- )) as [NumericChartData];
+ let cpTimeSeries: NumericChartData;
+
+ try {
+ cpTimeSeries = (
+ (await fetchHistogramsForFields(
+ client,
+ request.body.index,
+ histogramQuery,
+ // fields
+ [
+ {
+ fieldName: request.body.timeFieldName,
+ type: KBN_FIELD_TYPES.DATE,
+ interval: overallTimeSeries.interval,
+ min: overallTimeSeries.stats[0],
+ max: overallTimeSeries.stats[1],
+ },
+ ],
+ // samplerShardSize
+ -1,
+ undefined
+ )) as [NumericChartData]
+ )[0];
+ } catch (e) {
+ logger.error(
+ `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${
+ cp.fieldValue
+ }", got: \n${e.toString()}`
+ );
+ pushError(
+ `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".`
+ );
+ return;
+ }
const histogram =
overallTimeSeries.data.map((o, i) => {
diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts
index 03242a4bc8ae5..0fb7f90c89c12 100644
--- a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts
+++ b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts
@@ -8,6 +8,7 @@ import { uniqBy } from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ElasticsearchClient } from '@kbn/core/server';
+import type { Logger } from '@kbn/logging';
import { ChangePoint } from '@kbn/ml-agg-utils';
import { SPIKE_ANALYSIS_THRESHOLD } from '../../../common/constants';
import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes';
@@ -92,7 +93,9 @@ interface Aggs extends estypes.AggregationsSignificantLongTermsAggregate {
export const fetchChangePointPValues = async (
esClient: ElasticsearchClient,
params: AiopsExplainLogRateSpikesSchema,
- fieldNames: string[]
+ fieldNames: string[],
+ logger: Logger,
+ emitError: (m: string) => void
): Promise => {
const result: ChangePoint[] = [];
@@ -101,7 +104,16 @@ export const fetchChangePointPValues = async (
const resp = await esClient.search(request);
if (resp.aggregations === undefined) {
- throw new Error('fetchChangePoint failed, did not return aggregations.');
+ logger.error(
+ `Failed to fetch p-value aggregation for fieldName "${fieldName}", got: \n${JSON.stringify(
+ resp,
+ null,
+ 2
+ )}`
+ );
+ emitError(`Failed to fetch p-value aggregation for fieldName "${fieldName}".`);
+ // Still continue the analysis even if individual p-value queries fail.
+ continue;
}
const overallResult = resp.aggregations.change_point_p_value;
diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts
index 055c22397064f..c9444aaca22af 100644
--- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts
+++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts
@@ -10,6 +10,7 @@ import { uniq, uniqWith, pick, isEqual } from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
+import type { Logger } from '@kbn/logging';
import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils';
interface FrequentItemsAggregation extends estypes.AggregationsSamplerAggregation {
@@ -53,9 +54,11 @@ export async function fetchFrequentItems(
changePoints: ChangePoint[],
timeFieldName: string,
deviationMin: number,
- deviationMax: number
+ deviationMax: number,
+ logger: Logger,
+ emitError: (m: string) => void
) {
- // get unique fields that are left
+ // get unique fields from change points
const fields = [...new Set(changePoints.map((t) => t.fieldName))];
// TODO add query params
@@ -91,6 +94,8 @@ export async function fetchFrequentItems(
sampleProbability = Math.min(0.5, minDocCount / totalDocCount);
}
+ logger.debug(`frequent_items sample probability: ${sampleProbability}`);
+
// frequent items can be slow, so sample and use 10% min_support
const aggs: Record = {
sample: {
@@ -103,7 +108,7 @@ export async function fetchFrequentItems(
frequent_items: {
minimum_set_size: 2,
size: 200,
- minimum_support: 0.01,
+ minimum_support: 0.1,
fields: aggFields,
},
},
@@ -125,12 +130,18 @@ export async function fetchFrequentItems(
{ maxRetries: 0 }
);
- const totalDocCountFi = (body.hits.total as estypes.SearchTotalHits).value;
-
if (body.aggregations === undefined) {
- throw new Error('fetchFrequentItems failed, did not return aggregations.');
+ logger.error(`Failed to fetch frequent_items, got: \n${JSON.stringify(body, null, 2)}`);
+ emitError(`Failed to fetch frequent_items.`);
+ return {
+ fields: [],
+ df: [],
+ totalDocCount: 0,
+ };
}
+ const totalDocCountFi = (body.hits.total as estypes.SearchTotalHits).value;
+
const shape = body.aggregations.sample.fi.buckets.length;
let maximum = shape;
if (maximum > 50000) {
diff --git a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts
index b4bdf9f50beb5..a2e1f158a73e2 100644
--- a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts
+++ b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts
@@ -222,9 +222,7 @@ export default ({ getService }: FtrProviderContext) => {
const errorActions = data.filter((d) => d.type === expected.errorFilter);
expect(errorActions.length).to.be(1);
- expect(errorActions[0].payload).to.be(
- 'ResponseError: index_not_found_exception: [index_not_found_exception] Reason: no such index [does_not_exist]'
- );
+ expect(errorActions[0].payload).to.be('Failed to fetch field candidates.');
});
});
};
From 87dc1fa82f4654defcac1834bd722d6330e91036 Mon Sep 17 00:00:00 2001
From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com>
Date: Tue, 4 Oct 2022 13:57:00 +0200
Subject: [PATCH 08/39] [Security Solution][Endpoint][Response Actions] Add
license check to actions log management RBAC (#142482)
* Add license check to actions log management RBAC
fixes elastic/security-team/issues/5118
refs elastic/kibana/pull/142470
* useUSerPrivileges instead
review changes (@paul-tavares)
* Don't register route if no access
review changes (@paul-tavares)
* reset mocked privilege
review changes (@paul-tavares)
---
.../common/endpoint/service/authz/authz.ts | 2 +-
.../common/components/navigation/types.ts | 2 +-
.../index.test.tsx | 28 +++++++++++++++++++
.../use_navigation_items.tsx | 8 +++++-
.../public/management/links.test.ts | 19 ++++++++++++-
.../public/management/links.ts | 17 +++++++----
.../public/management/pages/index.tsx | 13 +++++----
7 files changed, 75 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts
index dde2a7f92b1e0..d25fd440d1c24 100644
--- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts
@@ -160,7 +160,7 @@ export const calculateEndpointAuthz = (
canWritePolicyManagement,
canReadPolicyManagement,
canWriteActionsLogManagement,
- canReadActionsLogManagement,
+ canReadActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense,
// Response Actions
canIsolateHost: canIsolateHost && isPlatinumPlusLicense,
canUnIsolateHost: canIsolateHost,
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
index ebfae21d5a5e5..5a2d192b9fd48 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts
@@ -68,7 +68,6 @@ export interface NavTab {
}
export const securityNavKeys = [
SecurityPageName.alerts,
- SecurityPageName.responseActionsHistory,
SecurityPageName.blocklist,
SecurityPageName.detectionAndResponse,
SecurityPageName.case,
@@ -81,6 +80,7 @@ export const securityNavKeys = [
SecurityPageName.hosts,
SecurityPageName.network,
SecurityPageName.overview,
+ SecurityPageName.responseActionsHistory,
SecurityPageName.rules,
SecurityPageName.timelines,
SecurityPageName.trustedApps,
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
index 1055c98835d56..5a99df01e5328 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx
@@ -17,6 +17,7 @@ import { TestProviders } from '../../../mock';
import { CASES_FEATURE_ID } from '../../../../../common/constants';
import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks';
import { useTourContext } from '../../guided_onboarding';
+import { useUserPrivileges } from '../../user_privileges';
import {
noCasesPermissions,
readCasesCapabilities,
@@ -38,6 +39,9 @@ jest.mock('../../../hooks/use_experimental_features');
jest.mock('../../../utils/route/use_route_spy');
jest.mock('../../../../management/pages/host_isolation_exceptions/view/hooks');
jest.mock('../../guided_onboarding');
+jest.mock('../../user_privileges');
+
+const mockUseUserPrivileges = useUserPrivileges as jest.Mock;
describe('useSecuritySolutionNavigation', () => {
const mockRouteSpy = [
@@ -56,6 +60,9 @@ describe('useSecuritySolutionNavigation', () => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
(useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy);
(useCanSeeHostIsolationExceptionsMenu as jest.Mock).mockReturnValue(true);
+ mockUseUserPrivileges.mockImplementation(() => ({
+ endpointPrivileges: { canReadActionsLogManagement: true },
+ }));
(useTourContext as jest.Mock).mockReturnValue({ isTourShown: false });
const cases = mockCasesContract();
@@ -83,6 +90,10 @@ describe('useSecuritySolutionNavigation', () => {
});
});
+ afterEach(() => {
+ mockUseUserPrivileges.mockReset();
+ });
+
it('should create navigation config', async () => {
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
() => useSecuritySolutionNavigation(),
@@ -117,6 +128,23 @@ describe('useSecuritySolutionNavigation', () => {
).toBeUndefined();
});
+ it('should omit response actions history if hook reports false', () => {
+ mockUseUserPrivileges.mockImplementation(() => ({
+ endpointPrivileges: { canReadActionsLogManagement: false },
+ }));
+ const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
+ () => useSecuritySolutionNavigation(),
+ { wrapper: TestProviders }
+ );
+ const items = result.current?.items;
+ expect(items).toBeDefined();
+ expect(
+ items!
+ .find((item) => item.id === 'manage')
+ ?.items?.find((item) => item.id === 'response_actions_history')
+ ).toBeUndefined();
+ });
+
describe('Permission gated routes', () => {
describe('cases', () => {
it('should display the cases navigation item when the user has read permissions', () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
index dc15e371ba630..a4364c8564529 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx
@@ -21,6 +21,7 @@ import { SecurityPageName } from '../../../../../common/constants';
import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks';
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
import { useGlobalQueryString } from '../../../utils/global_query_string';
+import { useUserPrivileges } from '../../user_privileges';
export const usePrimaryNavigationItems = ({
navTabs,
@@ -71,6 +72,8 @@ export const usePrimaryNavigationItems = ({
function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
const hasCasesReadPermissions = useGetUserCasesPermissions().read;
const canSeeHostIsolationExceptions = useCanSeeHostIsolationExceptionsMenu();
+ const canSeeResponseActionsHistory =
+ useUserPrivileges().endpointPrivileges.canReadActionsLogManagement;
const isPolicyListEnabled = useIsExperimentalFeatureEnabled('policyListEnabled');
const uiCapabilities = useKibana().services.application.capabilities;
@@ -138,7 +141,9 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
? [navTabs[SecurityPageName.hostIsolationExceptions]]
: []),
navTabs[SecurityPageName.blocklist],
- navTabs[SecurityPageName.responseActionsHistory],
+ ...(canSeeResponseActionsHistory
+ ? [navTabs[SecurityPageName.responseActionsHistory]]
+ : []),
navTabs[SecurityPageName.cloudSecurityPostureBenchmarks],
],
},
@@ -156,6 +161,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) {
navTabs,
hasCasesReadPermissions,
canSeeHostIsolationExceptions,
+ canSeeResponseActionsHistory,
isPolicyListEnabled,
]
);
diff --git a/x-pack/plugins/security_solution/public/management/links.test.ts b/x-pack/plugins/security_solution/public/management/links.test.ts
index 09c47bc70095c..c8166563428ab 100644
--- a/x-pack/plugins/security_solution/public/management/links.test.ts
+++ b/x-pack/plugins/security_solution/public/management/links.test.ts
@@ -80,13 +80,30 @@ describe('links', () => {
expect(filteredLinks).toEqual(links);
});
+ it('it returns all but response actions history when no access privilege to either response actions history or HIE but have at least one HIE entry', async () => {
+ fakeHttpServices.get.mockResolvedValue({ total: 1 });
+ const filteredLinks = await getManagementFilteredLinks(
+ coreMockStarted,
+ getPlugins(['superuser'])
+ );
+ (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
+ expect(filteredLinks).toEqual({
+ ...links,
+ links: links.links?.filter((link) => link.id !== SecurityPageName.responseActionsHistory),
+ });
+ });
+
it('it returns filtered links when not having isolation permissions and no host isolation exceptions entry', async () => {
fakeHttpServices.get.mockResolvedValue({ total: 0 });
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([]));
expect(filteredLinks).toEqual({
...links,
- links: links.links?.filter((link) => link.id !== SecurityPageName.hostIsolationExceptions),
+ links: links.links?.filter(
+ (link) =>
+ link.id !== SecurityPageName.hostIsolationExceptions &&
+ link.id !== SecurityPageName.responseActionsHistory
+ ),
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts
index 03cfee736def3..12a904201a9c5 100644
--- a/x-pack/plugins/security_solution/public/management/links.ts
+++ b/x-pack/plugins/security_solution/public/management/links.ts
@@ -226,7 +226,7 @@ export const links: LinkItem = {
],
};
-const getFilteredLinks = (linkIds: SecurityPageName[]) => ({
+const excludeLinks = (linkIds: SecurityPageName[]) => ({
...links,
links: links.links?.filter((link) => !linkIds.includes(link.id)),
});
@@ -249,19 +249,26 @@ export const getManagementFilteredLinks = async (
)
: getEndpointAuthzInitialState();
if (!privileges.canAccessEndpointManagement) {
- return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
+ return excludeLinks([
+ SecurityPageName.hostIsolationExceptions,
+ SecurityPageName.responseActionsHistory,
+ ]);
}
- if (!privileges.canIsolateHost) {
+ if (!privileges.canIsolateHost || !privileges.canReadActionsLogManagement) {
const hostIsolationExceptionsApiClientInstance = HostIsolationExceptionsApiClient.getInstance(
core.http
);
const summaryResponse = await hostIsolationExceptionsApiClientInstance.summary();
if (!summaryResponse.total) {
- return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
+ return excludeLinks([
+ SecurityPageName.hostIsolationExceptions,
+ SecurityPageName.responseActionsHistory,
+ ]);
}
+ return excludeLinks([SecurityPageName.responseActionsHistory]);
}
} catch {
- return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
+ return excludeLinks([SecurityPageName.hostIsolationExceptions]);
}
return links;
diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx
index dd06a838a26cb..590b3786ece15 100644
--- a/x-pack/plugins/security_solution/public/management/pages/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx
@@ -76,7 +76,8 @@ const ResponseActionsTelemetry = () => (
);
export const ManagementContainer = memo(() => {
- const { loading, canAccessEndpointManagement } = useUserPrivileges().endpointPrivileges;
+ const { loading, canAccessEndpointManagement, canReadActionsLogManagement } =
+ useUserPrivileges().endpointPrivileges;
// Lets wait until we can verify permissions
if (loading) {
@@ -103,10 +104,12 @@ export const ManagementContainer = memo(() => {
component={HostIsolationExceptionsTelemetry}
/>
-
+ {canReadActionsLogManagement && (
+
+ )}
From 8be7668d208073a80812397050298a6954c51a92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20R=C3=BChsen?=
Date: Tue, 4 Oct 2022 13:59:28 +0200
Subject: [PATCH 09/39] [Profiling] Show Top 1000 functions (#142391)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/profiling/public/components/topn_functions.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/profiling/public/components/topn_functions.tsx b/x-pack/plugins/profiling/public/components/topn_functions.tsx
index 3ad540983d903..4d8522913a245 100644
--- a/x-pack/plugins/profiling/public/components/topn_functions.tsx
+++ b/x-pack/plugins/profiling/public/components/topn_functions.tsx
@@ -219,7 +219,7 @@ export const TopNFunctionsTable = ({
: row[sortField];
},
[sortDirection]
- ).slice(0, 100);
+ );
return (
<>
From 8e770bb6080b99b5437b22b22fc0d07c68e4c504 Mon Sep 17 00:00:00 2001
From: Uladzislau Lasitsa
Date: Tue, 4 Oct 2022 15:00:59 +0300
Subject: [PATCH 10/39] [TSVB][Lens]Fix conversion from static value in
timeseries to reference line in lens (#142453)
* Fix conversion static value in timeseries to reference line in lens
* Doesn't allow convert static value with split
* Fix condition
* Ignore axis position from model for top n
* Added tests
Co-authored-by: Stratoula Kalafateli
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../lib/configurations/xy/layers.test.ts | 131 ++++++++++++------
.../lib/configurations/xy/layers.ts | 37 +++--
.../convert_to_lens/timeseries/index.test.ts | 13 ++
.../convert_to_lens/timeseries/index.ts | 12 +-
.../public/convert_to_lens/top_n/index.ts | 2 +-
5 files changed, 138 insertions(+), 57 deletions(-)
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts
index 6c94971397d3e..46e9d9e1fae2a 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts
@@ -24,6 +24,33 @@ jest.mock('uuid', () => ({
v4: () => 'test-id',
}));
+const mockedIndices = [
+ {
+ id: 'test',
+ title: 'test',
+ timeFieldName: 'test_field',
+ getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
+ },
+] as unknown as DataView[];
+
+const indexPatternsService = {
+ getDefault: jest.fn(() =>
+ Promise.resolve({
+ id: 'default',
+ title: 'index',
+ getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
+ })
+ ),
+ get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })),
+ find: jest.fn((search: string, size: number) => {
+ if (size !== 1) {
+ // shouldn't request more than one data view since there is a significant performance penalty
+ throw new Error('trying to fetch too many data views');
+ }
+ return Promise.resolve(mockedIndices || []);
+ }),
+} as unknown as DataViewsPublicPluginStart;
+
describe('getLayers', () => {
const dataSourceLayers: Record = [
{
@@ -331,10 +358,16 @@ describe('getLayers', () => {
series: [createSeries({ metrics: staticValueMetric })],
});
- test.each<[string, [Record, Panel], Array>]>([
+ test.each<
+ [
+ string,
+ [Record, Panel, DataViewsPublicPluginStart, boolean],
+ Array>
+ ]
+ >([
[
'data layer if columns do not include static column',
- [dataSourceLayers, panel],
+ [dataSourceLayers, panel, indexPatternsService, false],
[
{
layerType: 'data',
@@ -353,9 +386,30 @@ describe('getLayers', () => {
},
],
],
+ [
+ 'data layer with "left" axisMode if isSingleAxis is provided',
+ [dataSourceLayers, panel, indexPatternsService, true],
+ [
+ {
+ layerType: 'data',
+ accessors: ['column-id-1'],
+ xAccessor: 'column-id-2',
+ splitAccessor: 'column-id-3',
+ seriesType: 'area',
+ layerId: 'test-layer-1',
+ yConfig: [
+ {
+ forAccessor: 'column-id-1',
+ axisMode: 'left',
+ color: '#68BC00',
+ },
+ ],
+ },
+ ],
+ ],
[
'reference line layer if columns include static column',
- [dataSourceLayersWithStatic, panelWithStaticValue],
+ [dataSourceLayersWithStatic, panelWithStaticValue, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@@ -364,9 +418,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -374,7 +429,7 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile columns',
- [dataSourceLayersWithPercentile, panelWithPercentileMetric],
+ [dataSourceLayersWithPercentile, panelWithPercentileMetric, indexPatternsService, false],
[
{
yConfig: [
@@ -394,7 +449,12 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile rank columns',
- [dataSourceLayersWithPercentileRank, panelWithPercentileRankMetric],
+ [
+ dataSourceLayersWithPercentileRank,
+ panelWithPercentileRankMetric,
+ indexPatternsService,
+ false,
+ ],
[
{
yConfig: [
@@ -414,7 +474,7 @@ describe('getLayers', () => {
],
[
'annotation layer gets correct params and converts color, extraFields and icons',
- [dataSourceLayersWithStatic, panelWithSingleAnnotation],
+ [dataSourceLayersWithStatic, panelWithSingleAnnotation, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@@ -423,9 +483,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -459,7 +520,12 @@ describe('getLayers', () => {
],
[
'annotation layer should gets correct default params',
- [dataSourceLayersWithStatic, panelWithSingleAnnotationWithoutQueryStringAndTimefield],
+ [
+ dataSourceLayersWithStatic,
+ panelWithSingleAnnotationWithoutQueryStringAndTimefield,
+ indexPatternsService,
+ false,
+ ],
[
{
layerType: 'referenceLine',
@@ -468,9 +534,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -504,7 +571,7 @@ describe('getLayers', () => {
],
[
'multiple annotations with different data views create separate layers',
- [dataSourceLayersWithStatic, panelWithMultiAnnotations],
+ [dataSourceLayersWithStatic, panelWithMultiAnnotations, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@@ -513,9 +580,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -598,7 +666,12 @@ describe('getLayers', () => {
],
[
'annotation layer gets correct dataView when none is defined',
- [dataSourceLayersWithStatic, panelWithSingleAnnotationDefaultDataView],
+ [
+ dataSourceLayersWithStatic,
+ panelWithSingleAnnotationDefaultDataView,
+ indexPatternsService,
+ false,
+ ],
[
{
layerType: 'referenceLine',
@@ -607,9 +680,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
- axisMode: 'right',
+ axisMode: 'left',
color: '#68BC00',
fill: 'below',
+ lineWidth: 1,
},
],
},
@@ -642,34 +716,7 @@ describe('getLayers', () => {
],
],
])('should return %s', async (_, input, expected) => {
- const layers = await getLayers(...input, indexPatternsService as DataViewsPublicPluginStart);
+ const layers = await getLayers(...input);
expect(layers).toEqual(expected.map(expect.objectContaining));
});
});
-
-const mockedIndices = [
- {
- id: 'test',
- title: 'test',
- timeFieldName: 'test_field',
- getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
- },
-] as unknown as DataView[];
-
-const indexPatternsService = {
- getDefault: jest.fn(() =>
- Promise.resolve({
- id: 'default',
- title: 'index',
- getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
- })
- ),
- get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })),
- find: jest.fn((search: string, size: number) => {
- if (size !== 1) {
- // shouldn't request more than one data view since there is a significant performance penalty
- throw new Error('trying to fetch too many data views');
- }
- return Promise.resolve(mockedIndices || []);
- }),
-} as unknown as DataViewsPublicPluginStart;
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts
index ec0e24e2db873..8784c2952807d 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts
@@ -24,7 +24,7 @@ import { getDefaultQueryLanguage } from '../../../../application/components/lib/
import { fetchIndexPattern } from '../../../../../common/index_patterns_utils';
import { ICON_TYPES_MAP } from '../../../../application/visualizations/constants';
import { SUPPORTED_METRICS } from '../../metrics';
-import type { Annotation, Metric, Panel } from '../../../../../common/types';
+import type { Annotation, Metric, Panel, Series } from '../../../../../common/types';
import { getSeriesAgg } from '../../series';
import {
isPercentileRanksColumnWithMeta,
@@ -44,6 +44,10 @@ function getPalette(palette: PaletteOutput): PaletteOutput {
: palette;
}
+function getAxisMode(series: Series, model: Panel): YAxisMode {
+ return (series.separate_axis ? series.axis_position : model.axis_position) as YAxisMode;
+}
+
function getColor(
metricColumn: Column,
metric: Metric,
@@ -69,7 +73,8 @@ function nonNullable(value: T): value is NonNullable {
export const getLayers = async (
dataSourceLayers: Record,
model: Panel,
- dataViews: DataViewsPublicPluginStart
+ dataViews: DataViewsPublicPluginStart,
+ isSingleAxis: boolean = false
): Promise => {
const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => {
const series = model.series[parseInt(key, 10)];
@@ -84,13 +89,13 @@ export const getLayers = async (
const metricColumns = dataSourceLayer.columns.filter(
(l) => !l.isBucketed && l.columnId !== referenceColumnId
);
- const isReferenceLine = metrics.length === 1 && metrics[0].type === 'static';
+ const isReferenceLine =
+ metricColumns.length === 1 && metricColumns[0].operationType === 'static_value';
const splitAccessor = dataSourceLayer.columns.find(
(column) => column.isBucketed && column.isSplit
)?.columnId;
const chartType = getChartType(series, model.type);
const commonProps = {
- seriesType: chartType,
layerId: dataSourceLayer.layerId,
accessors: metricColumns.map((metricColumn) => {
return metricColumn.columnId;
@@ -102,19 +107,19 @@ export const getLayers = async (
return {
forAccessor: metricColumn.columnId,
color: getColor(metricColumn, metric!, series.color, splitAccessor),
- axisMode: (series.separate_axis
- ? series.axis_position
- : model.axis_position) as YAxisMode,
+ axisMode: isReferenceLine // reference line should be assigned to axis with real data
+ ? model.series.some((s) => s.id !== series.id && getAxisMode(s, model) === 'right')
+ ? 'right'
+ : 'left'
+ : isSingleAxis
+ ? 'left'
+ : getAxisMode(series, model),
...(isReferenceLine && {
- fill: chartType === 'area' ? FillTypes.BELOW : FillTypes.NONE,
+ fill: chartType.includes('area') ? FillTypes.BELOW : FillTypes.NONE,
+ lineWidth: series.line_width,
}),
};
}),
- xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit)
- ?.columnId,
- splitAccessor,
- collapseFn: seriesAgg,
- palette: getPalette(series.palette as PaletteOutput),
};
if (isReferenceLine) {
return {
@@ -123,8 +128,14 @@ export const getLayers = async (
};
} else {
return {
+ seriesType: chartType,
layerType: 'data',
...commonProps,
+ xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit)
+ ?.columnId,
+ splitAccessor,
+ collapseFn: seriesAgg,
+ palette: getPalette(series.palette as PaletteOutput),
};
}
});
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts
index 50aa1a6c6f7f4..c81db38e05384 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts
@@ -112,6 +112,19 @@ describe('convertToLens', () => {
expect(mockGetBucketsColumns).toBeCalledTimes(1);
});
+ test('should return null for static value with buckets', async () => {
+ mockGetBucketsColumns.mockReturnValue([{}]);
+ mockGetMetricsColumns.mockReturnValue([
+ {
+ operationType: 'static_value',
+ },
+ ]);
+ const result = await convertToLens(model);
+ expect(result).toBeNull();
+ expect(mockGetMetricsColumns).toBeCalledTimes(1);
+ expect(mockGetBucketsColumns).toBeCalledTimes(1);
+ });
+
test('should return state for valid model', async () => {
const result = await convertToLens(model);
expect(result).toBeDefined();
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts
index 8cbbbf0f9e739..ef678fcc2dab4 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts
@@ -98,11 +98,21 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel
return null;
}
+ const isReferenceLine =
+ metricsColumns.length === 1 && metricsColumns[0].operationType === 'static_value';
+
+ // only static value without split is supported
+ if (isReferenceLine && bucketsColumns.length) {
+ return null;
+ }
+
const layerId = uuid();
extendedLayers[layerIdx] = {
indexPatternId,
layerId,
- columns: [...metricsColumns, dateHistogramColumn, ...bucketsColumns],
+ columns: isReferenceLine
+ ? [...metricsColumns]
+ : [...metricsColumns, dateHistogramColumn, ...bucketsColumns],
columnOrder: [],
};
}
diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts
index 020aaec28f573..130646f72f127 100644
--- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts
+++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts
@@ -86,7 +86,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR
};
}
- const configLayers = await getLayers(extendedLayers, model, dataViews);
+ const configLayers = await getLayers(extendedLayers, model, dataViews, true);
if (configLayers === null) {
return null;
}
From bcfa351f06037532dbc7d9ca6f06eaa5193a60d2 Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 4 Oct 2022 08:17:17 -0400
Subject: [PATCH 11/39] Allow null description field value to round trip from
server (#142540)
---
.../update_connector_name_and_description_api_logic.ts | 8 +++-----
.../connector_name_and_description.tsx | 2 +-
.../server/routes/enterprise_search/connectors.ts | 4 ++--
3 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts
index caf19f80f040a..639bd56208546 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts
@@ -16,17 +16,15 @@ export type PutConnectorNameAndDescriptionArgs = Partial<
indexName: string;
};
-export type PutConnectorNameAndDescriptionResponse = Partial<
- Pick
-> & {
+export type PutConnectorNameAndDescriptionResponse = Pick & {
indexName: string;
};
export const putConnectorNameAndDescription = async ({
connectorId,
- description,
+ description = null,
indexName,
- name,
+ name = '',
}: PutConnectorNameAndDescriptionArgs) => {
const route = `/internal/enterprise_search/connectors/${connectorId}/name_and_description`;
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx
index d75482e25e784..9f6c96ce75e0a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx
@@ -65,7 +65,7 @@ export const ConnectorNameAndDescription: React.FC = () => {
title: NAME_LABEL,
},
{
- description: description ?? '--',
+ description: description || '--',
title: DESCRIPTION_LABEL,
},
]}
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
index 0aaf30ef126d4..9663b216ec91c 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
@@ -254,8 +254,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
connectorId: schema.string(),
}),
body: schema.object({
- name: schema.maybe(schema.string()),
- description: schema.maybe(schema.string()),
+ name: schema.string(),
+ description: schema.nullable(schema.string()),
}),
},
},
From 24ce456ec1ebd7713dcfccd569f0397a6e393999 Mon Sep 17 00:00:00 2001
From: doakalexi <109488926+doakalexi@users.noreply.github.com>
Date: Tue, 4 Oct 2022 08:22:38 -0400
Subject: [PATCH 12/39] Fixing flaky test (#142498)
---
.../group2/tests/telemetry/alerting_and_actions_telemetry.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts
index c89e5b48b236b..b4cb36ab59d85 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts
@@ -235,8 +235,9 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F
// number of action executions broken down by connector type
expect(telemetry.count_actions_executions_by_type_per_day['test.throw'] > 0).to.be(true);
- // average execution time - just checking for non-zero as we can't set an exact number
- expect(telemetry.avg_execution_time_per_day > 0).to.be(true);
+ // average execution time - just checking for a positive number as we can't set an exact number
+ // if the time is less than 1ms it will round down to 0
+ expect(telemetry.avg_execution_time_per_day >= 0).to.be(true);
// average execution time broken down by rule type
expect(telemetry.avg_execution_time_by_type_per_day['test.throw'] > 0).to.be(true);
From 6f8e758f113d7f22cc1df1ec4449b3d0f01da50d Mon Sep 17 00:00:00 2001
From: Ying Mao
Date: Tue, 4 Oct 2022 08:25:32 -0400
Subject: [PATCH 13/39] =?UTF-8?q?Fixing=20Failing=20test:=20X-Pack=20Alert?=
=?UTF-8?q?ing=20API=20Integration=20Tests.x-pack/test/alerting=5Fapi=5Fin?=
=?UTF-8?q?tegration/security=5Fand=5Fspaces/group1/tests/alerting/disable?=
=?UTF-8?q?=C2=B7ts=20-=20alerting=20api=20integration=20security=20and=20?=
=?UTF-8?q?spaces=20enabled=20Alerts=20-=20Group=201=20alerts=20disable=20?=
=?UTF-8?q?superuser=20at=20space1=20should=20still=20be=20able=20to=20dis?=
=?UTF-8?q?able=20alert=20when=20AAD=20is=20broken=20(#142483)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Unskipping test
* Adding retries
---
.../group1/tests/alerting/disable.ts | 71 ++++++++++---------
1 file changed, 38 insertions(+), 33 deletions(-)
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts
index 860576f806e38..df9fc34e17014 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts
@@ -26,8 +26,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
- // Failing: See https://github.com/elastic/kibana/issues/140797
- describe.skip('disable', () => {
+ describe('disable', () => {
const objectRemover = new ObjectRemover(supertest);
after(() => objectRemover.removeAll());
@@ -110,21 +109,23 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
expect(response.body).to.eql('');
// task should still exist but be disabled
- const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
- expect(taskRecord2.type).to.eql('task');
- expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
- expect(JSON.parse(taskRecord2.task.params)).to.eql({
- alertId: createdAlert.id,
- spaceId: space.id,
- consumer: 'alertsFixture',
- });
- expect(taskRecord2.task.enabled).to.eql(false);
- // Ensure AAD isn't broken
- await checkAAD({
- supertest,
- spaceId: space.id,
- type: 'alert',
- id: createdAlert.id,
+ await retry.try(async () => {
+ const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
+ expect(taskRecord2.type).to.eql('task');
+ expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
+ expect(JSON.parse(taskRecord2.task.params)).to.eql({
+ alertId: createdAlert.id,
+ spaceId: space.id,
+ consumer: 'alertsFixture',
+ });
+ expect(taskRecord2.task.enabled).to.eql(false);
+ // Ensure AAD isn't broken
+ await checkAAD({
+ supertest,
+ spaceId: space.id,
+ type: 'alert',
+ id: createdAlert.id,
+ });
});
break;
default:
@@ -295,15 +296,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
expect(response.statusCode).to.eql(204);
expect(response.body).to.eql('');
// task should still exist but be disabled
- const taskRecord = await getScheduledTask(createdAlert.scheduled_task_id);
- expect(taskRecord.type).to.eql('task');
- expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
- expect(JSON.parse(taskRecord.task.params)).to.eql({
- alertId: createdAlert.id,
- spaceId: space.id,
- consumer: 'alerts',
+ await retry.try(async () => {
+ const taskRecord = await getScheduledTask(createdAlert.scheduled_task_id);
+ expect(taskRecord.type).to.eql('task');
+ expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
+ expect(JSON.parse(taskRecord.task.params)).to.eql({
+ alertId: createdAlert.id,
+ spaceId: space.id,
+ consumer: 'alerts',
+ });
+ expect(taskRecord.task.enabled).to.eql(false);
});
- expect(taskRecord.task.enabled).to.eql(false);
break;
default:
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
@@ -366,15 +369,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
expect(response.statusCode).to.eql(204);
expect(response.body).to.eql('');
// task should still exist but be disabled
- const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
- expect(taskRecord2.type).to.eql('task');
- expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
- expect(JSON.parse(taskRecord2.task.params)).to.eql({
- alertId: createdAlert.id,
- spaceId: space.id,
- consumer: 'alertsFixture',
+ await retry.try(async () => {
+ const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id);
+ expect(taskRecord2.type).to.eql('task');
+ expect(taskRecord2.task.taskType).to.eql('alerting:test.noop');
+ expect(JSON.parse(taskRecord2.task.params)).to.eql({
+ alertId: createdAlert.id,
+ spaceId: space.id,
+ consumer: 'alertsFixture',
+ });
+ expect(taskRecord2.task.enabled).to.eql(false);
});
- expect(taskRecord2.task.enabled).to.eql(false);
break;
default:
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
From fb6b10e23210a820f19e6e69684ba0b90730099e Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Tue, 4 Oct 2022 13:29:00 +0100
Subject: [PATCH 14/39] skip flaky suite (#142110)
---
.../api_integration/apis/uptime/rest/add_monitor_project.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts
index 9dce7e7d8fdaa..a8eec4c568dc9 100644
--- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts
+++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts
@@ -19,7 +19,8 @@ import { PrivateLocationTestService } from './services/private_location_test_ser
import { comparePolicies, getTestProjectSyntheticsPolicy } from './sample_data/test_policy';
export default function ({ getService }: FtrProviderContext) {
- describe('AddProjectMonitors', function () {
+ // FLAKY: https://github.com/elastic/kibana/issues/142110
+ describe.skip('AddProjectMonitors', function () {
this.tags('skipCloud');
const supertest = getService('supertest');
From b62c0f98cf8b82d4124518983abedf76babf5843 Mon Sep 17 00:00:00 2001
From: Rickyanto Ang
Date: Tue, 4 Oct 2022 05:33:39 -0700
Subject: [PATCH 15/39] added beta tag for linux config (#142171)
---
.../policy_forms/components/policy_form_layout.test.tsx | 7 +++++++
.../pages/policy/view/policy_forms/events/linux.tsx | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx
index 49a2e8173476a..60dc7bd29895a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx
@@ -135,6 +135,13 @@ describe('Policy Form Layout', () => {
expect(saveButton).toHaveLength(1);
expect(saveButton.text()).toEqual('Save');
});
+ it('should display beta badge', async () => {
+ await asyncActions;
+ policyFormLayoutView.update();
+ const saveButton = policyFormLayoutView.find('EuiBetaBadge');
+ expect(saveButton).toHaveLength(1);
+ expect(saveButton.text()).toEqual('beta');
+ });
describe('when the save button is clicked', () => {
let saveButton: FindReactWrapperResponse;
let confirmModal: FindReactWrapperResponse;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx
index 1b8e4f2040150..984bc53a014e3 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx
@@ -88,7 +88,7 @@ const SUPPLEMENTAL_OPTIONS: ReadonlyArray {
return !config.linux.events.session_data;
},
- beta: false,
+ beta: true,
},
];
From 6824718c0a15347395d8e49db23b934b475b142d Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 4 Oct 2022 14:46:53 +0200
Subject: [PATCH 16/39] [Synthetics] Increase project API payload limit
(#142140)
---
src/plugins/bfetch/server/plugin.ts | 8 ++++++--
.../server/routes/monitor_cruds/add_monitor_project.ts | 7 +++++++
x-pack/plugins/synthetics/server/server.ts | 5 +++--
.../plugins/synthetics/server/synthetics_route_wrapper.ts | 1 +
4 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts
index 0f51f5da62353..85720480cf9a0 100644
--- a/src/plugins/bfetch/server/plugin.ts
+++ b/src/plugins/bfetch/server/plugin.ts
@@ -20,6 +20,7 @@ import {
} from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { map$ } from '@kbn/std';
+import { RouteConfigOptions } from '@kbn/core-http-server';
import {
StreamingResponseHandler,
BatchRequestData,
@@ -54,7 +55,8 @@ export interface BfetchServerSetup {
context: RequestHandlerContext
) => StreamingResponseHandler,
method?: 'GET' | 'POST' | 'PUT' | 'DELETE',
- pluginRouter?: ReturnType
+ pluginRouter?: ReturnType,
+ options?: RouteConfigOptions<'get' | 'post' | 'put' | 'delete'>
) => void;
}
@@ -117,14 +119,16 @@ export class BfetchServerPlugin
router: ReturnType;
logger: Logger;
}): BfetchServerSetup['addStreamingResponseRoute'] =>
- (path, handler, method = 'POST', pluginRouter) => {
+ (path, handler, method = 'POST', pluginRouter, options) => {
const httpRouter = pluginRouter || router;
+
const routeDefinition = {
path: `/${removeLeadingSlash(path)}`,
validate: {
body: schema.any(),
query: schema.object({ compress: schema.boolean({ defaultValue: false }) }),
},
+ options,
};
const routeHandler: RequestHandler = async (
context: RequestHandlerContext,
diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts
index 668d97a0819e3..ea269d87413e7 100644
--- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts
+++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts
@@ -13,6 +13,8 @@ import { API_URLS } from '../../../common/constants';
import { getAllLocations } from '../../synthetics_service/get_all_locations';
import { ProjectMonitorFormatter } from '../../synthetics_service/project_monitor/project_monitor_formatter';
+const MAX_PAYLOAD_SIZE = 1048576 * 20; // 20MiB
+
export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory = (
libs: UMServerLibs
) => ({
@@ -25,6 +27,11 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory =
monitors: schema.arrayOf(schema.any()),
}),
},
+ options: {
+ body: {
+ maxBytes: MAX_PAYLOAD_SIZE,
+ },
+ },
handler: async ({
request,
savedObjectsClient,
diff --git a/x-pack/plugins/synthetics/server/server.ts b/x-pack/plugins/synthetics/server/server.ts
index 12844c9cb9223..7f667e0fb264d 100644
--- a/x-pack/plugins/synthetics/server/server.ts
+++ b/x-pack/plugins/synthetics/server/server.ts
@@ -57,7 +57,7 @@ export const initSyntheticsServer = (
});
syntheticsAppStreamingApiRoutes.forEach((route) => {
- const { method, streamHandler, path } = syntheticsRouteWrapper(
+ const { method, streamHandler, path, options } = syntheticsRouteWrapper(
createSyntheticsRouteWithAuth(libs, route),
server,
syntheticsMonitorClient
@@ -82,7 +82,8 @@ export const initSyntheticsServer = (
};
},
method,
- server.router
+ server.router,
+ options
);
});
};
diff --git a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts
index 8706735fa9256..fc1376e157607 100644
--- a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts
@@ -19,6 +19,7 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = (
...uptimeRoute,
options: {
tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])],
+ ...(uptimeRoute.options ?? {}),
},
streamHandler: async (context, request, subject) => {
const coreContext = await context.core;
From 6875d18d0f1340d684e234ef866970bcb9ed087b Mon Sep 17 00:00:00 2001
From: Kurt
Date: Tue, 4 Oct 2022 08:50:12 -0400
Subject: [PATCH 17/39] Removing esArchiver in favor of testDataLoader for
`bulk_get` Saved Objects integration tests (#140998)
* Removing esArchiver in favor of testDataLoader
* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'
* Adding test data for loader
* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'
* Adding generic TestDataLoader
* Importing just the type per PR feedback
* Changing testDataLoader function names to be more descriptive
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Larry Gregory
Co-authored-by: Thomas Watson
---
.../common/lib/test_data_loader.ts | 46 ++---
.../fixtures/kbn_archiver/default_space.json | 163 ++++++++++++++++++
.../common/fixtures/kbn_archiver/space_1.json | 72 ++++++++
.../common/fixtures/kbn_archiver/space_2.json | 58 +++++++
.../common/suites/bulk_get.ts | 43 +++--
.../security_and_spaces/apis/bulk_get.ts | 11 +-
.../spaces_only/apis/bulk_get.ts | 7 +-
.../common/suites/copy_to_space.ts | 34 +++-
.../suites/resolve_copy_to_space_conflicts.ts | 29 +++-
9 files changed, 401 insertions(+), 62 deletions(-)
rename x-pack/test/{spaces_api_integration => }/common/lib/test_data_loader.ts (79%)
create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json
create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json
create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json
diff --git a/x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts b/x-pack/test/common/lib/test_data_loader.ts
similarity index 79%
rename from x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts
rename to x-pack/test/common/lib/test_data_loader.ts
index 4b25c722603c8..61c8ff4c1bf52 100644
--- a/x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts
+++ b/x-pack/test/common/lib/test_data_loader.ts
@@ -5,16 +5,14 @@
* 2.0.
*/
-import { FtrProviderContext } from '../ftr_provider_context';
-
-const SPACE_1 = {
+export const SPACE_1 = {
id: 'space_1',
name: 'Space 1',
description: 'This is the first test space',
disabledFeatures: [],
};
-const SPACE_2 = {
+export const SPACE_2 = {
id: 'space_2',
name: 'Space 2',
description: 'This is the second test space',
@@ -64,36 +62,38 @@ const OBJECTS_TO_SHARE: Array<{
},
];
-export function getTestDataLoader({ getService }: FtrProviderContext) {
+// @ts-ignore
+export function getTestDataLoader({ getService }) {
const spacesService = getService('spaces');
const kbnServer = getService('kibanaServer');
const supertest = getService('supertest');
const log = getService('log');
return {
- before: async () => {
+ createFtrSpaces: async () => {
await Promise.all([await spacesService.create(SPACE_1), await spacesService.create(SPACE_2)]);
},
- after: async () => {
+ deleteFtrSpaces: async () => {
await Promise.all([spacesService.delete(SPACE_1.id), spacesService.delete(SPACE_2.id)]);
},
- beforeEach: async () => {
+ createFtrSavedObjectsData: async (
+ spaceData: Array<{ spaceName: string | null; dataUrl: string }>
+ ) => {
log.debug('Loading test data for the following spaces: default, space_1 and space_2');
- await Promise.all([
- kbnServer.importExport.load(
- 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json'
- ),
- kbnServer.importExport.load(
- 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json',
- { space: SPACE_1.id }
- ),
- kbnServer.importExport.load(
- 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json',
- { space: SPACE_2.id }
- ),
- ]);
+
+ await Promise.all(
+ spaceData.map((spaceDataObj) => {
+ if (spaceDataObj.spaceName) {
+ return kbnServer.importExport.load(spaceDataObj.dataUrl, {
+ space: spaceDataObj.spaceName,
+ });
+ } else {
+ return kbnServer.importExport.load(spaceDataObj.dataUrl);
+ }
+ })
+ );
// Adjust spaces for the imported saved objects.
for (const { objects, spacesToAdd = [], spacesToRemove = [] } of OBJECTS_TO_SHARE) {
@@ -111,9 +111,9 @@ export function getTestDataLoader({ getService }: FtrProviderContext) {
}
},
- afterEach: async () => {
+ deleteFtrSavedObjectsData: async () => {
const allSpacesIds = [
- ...(await spacesService.getAll()).map((space) => space.id),
+ ...(await spacesService.getAll()).map((space: { id: string }) => space.id),
'non_existent_space',
];
log.debug(`Removing data from the following spaces: ${allSpacesIds.join(', ')}`);
diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json
new file mode 100644
index 0000000000000..9a2713fc61872
--- /dev/null
+++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json
@@ -0,0 +1,163 @@
+{
+ "attributes": {
+ "title": "logstash-*"
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "defaultspace-index-pattern-id",
+ "migrationVersion": {
+ "index-pattern": "8.0.0"
+ },
+ "originId": "cts_ip_1",
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyOCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Count of requests",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "version": 1,
+ "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}",
+ "description": "",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"index\":\"defaultspace-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ }
+ },
+ "id": "defaultspace-isolatedtype-id",
+ "references": [],
+ "type": "isolatedtype",
+ "updated_at": "2017-09-21T18:51:23.794Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Requests",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
+ }
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "defaultspace-dashboard-id",
+ "migrationVersion": {
+ "dashboard": "8.4.0"
+ },
+ "type": "dashboard",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyMCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A share-capable (isolated) saved-object only in the default space"
+ },
+ "id": "only_default_space",
+ "type": "sharecapabletype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object in all spaces"
+ },
+ "id": "all_spaces",
+ "type": "sharedtype",
+ "references": [],
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ5NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "My favorite global object"
+ },
+ "id": "globaltype-id",
+ "references": [],
+ "type": "globaltype",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object in the default and space_1 spaces"
+ },
+ "id": "default_and_space_1",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_1"
+ },
+ "id": "conflict_1",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_2a"
+ },
+ "id": "conflict_2a",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_2b"
+ },
+ "id": "conflict_2b",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_3"
+ },
+ "id": "conflict_3",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A sharedtype saved-object with id: conflict_4a"
+ },
+ "id": "conflict_4a",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Resolve outcome exactMatch"
+ },
+ "id": "exact-match",
+ "type": "resolvetype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Resolve outcome aliasMatch"
+ },
+ "id": "alias-match-newid",
+ "type": "resolvetype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json
new file mode 100644
index 0000000000000..6356d5c01989b
--- /dev/null
+++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json
@@ -0,0 +1,72 @@
+
+
+{
+ "attributes": {
+ "title": "logstash-*"
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space1-index-pattern-id",
+ "migrationVersion": {
+ "index-pattern": "8.0.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyOSwxXQ=="
+}
+
+{
+ "attributes": {
+ "description": "",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"index\":\"space1-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ },
+ "title": "Count of requests",
+ "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
+ "version": 1,
+ "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}"
+ },
+ "id": "space1-isolatedtype-id",
+ "references": [],
+ "type": "isolatedtype",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Requests",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
+ },
+ "version": 1
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space1-dashboard-id",
+ "migrationVersion": {
+ "dashboard": "8.4.0"
+ },
+ "type": "dashboard",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyMCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object only in space_1"
+ },
+ "id": "only_space_1",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A share-capable (isolated) saved-object only in space_1"
+ },
+ "id": "only_space_1",
+ "type": "sharecapabletype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json
new file mode 100644
index 0000000000000..9715a5f54d2b4
--- /dev/null
+++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json
@@ -0,0 +1,58 @@
+{
+ "attributes": {
+ "title": "logstash-*"
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space2-index-pattern-id",
+ "migrationVersion": {
+ "index-pattern": "8.0.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyOSwxXQ=="
+}
+
+{
+ "attributes": {
+ "description": "",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"index\":\"space2-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
+ },
+ "title": "Count of requests",
+ "version": 1
+ },
+ "id": "space2-isolatedtype-id",
+ "references": [],
+ "type": "isolatedtype",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzQ4NywxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "Requests",
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
+ },
+ "version": 1
+ },
+ "coreMigrationVersion": "8.4.0",
+ "id": "space2-dashboard-id",
+ "migrationVersion": {
+ "dashboard": "8.4.0"
+ },
+ "type": "dashboard",
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "version": "WzUyMCwxXQ=="
+}
+
+{
+ "attributes": {
+ "title": "A shared saved-object only in space_2"
+ },
+ "id": "only_space_2",
+ "type": "sharedtype",
+ "updated_at": "2017-09-21T18:59:16.270Z",
+ "version": "WzQ4OCwxXQ=="
+}
diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts
index 10709a6f20916..c9cb3b9739eee 100644
--- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts
+++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts
@@ -6,11 +6,12 @@
*/
import expect from '@kbn/expect';
-import { SuperTest } from 'supertest';
+import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader';
import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases';
import { SPACES } from '../lib/spaces';
import { expectResponses, getUrlPrefix, getTestTitle } from '../lib/saved_object_test_utils';
import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types';
+import type { FtrProviderContext } from '../ftr_provider_context';
export interface BulkGetTestDefinition extends TestDefinition {
request: Array<{ type: string; id: string }>;
@@ -33,7 +34,10 @@ const createRequest = ({ type, id, namespaces }: BulkGetTestCase) => ({
...(namespaces && { namespaces }), // individual "object namespaces" string array
});
-export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest) {
+export function bulkGetTestSuiteFactory(context: FtrProviderContext) {
+ const testDataLoader = getTestDataLoader(context);
+ const supertest = context.getService('supertestWithoutAuth');
+
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_get');
const expectResponseBody =
(testCases: BulkGetTestCase | BulkGetTestCase[], statusCode: 200 | 403): ExpectResponseBody =>
@@ -91,16 +95,31 @@ export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest {
- before(() =>
- esArchiver.load(
- 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces'
- )
- );
- after(() =>
- esArchiver.unload(
- 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces'
- )
- );
+ before(async () => {
+ await testDataLoader.createFtrSpaces();
+ await testDataLoader.createFtrSavedObjectsData([
+ {
+ spaceName: null,
+ dataUrl:
+ 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json',
+ },
+ {
+ spaceName: SPACE_1.id,
+ dataUrl:
+ 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json',
+ },
+ {
+ spaceName: SPACE_2.id,
+ dataUrl:
+ 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json',
+ },
+ ]);
+ });
+
+ after(async () => {
+ await testDataLoader.deleteFtrSpaces();
+ await testDataLoader.deleteFtrSavedObjectsData();
+ });
for (const test of tests) {
it(`should return ${test.responseStatusCode} ${test.title}`, async () => {
diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts
index 2c1fbf442b0ec..ed251440d361a 100644
--- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts
+++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts
@@ -67,14 +67,9 @@ const createTestCases = (spaceId: string) => {
return { normalTypes, crossNamespace, hiddenType, allTypes };
};
-export default function ({ getService }: FtrProviderContext) {
- const supertest = getService('supertestWithoutAuth');
- const esArchiver = getService('esArchiver');
-
- const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkGetTestSuiteFactory(
- esArchiver,
- supertest
- );
+export default function (context: FtrProviderContext) {
+ const { addTests, createTestDefinitions, expectSavedObjectForbidden } =
+ bulkGetTestSuiteFactory(context);
const createTests = (spaceId: string) => {
const { normalTypes, crossNamespace, hiddenType, allTypes } = createTestCases(spaceId);
// use singleRequest to reduce execution time and/or test combined cases
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts
index 41fa4749cc48e..30ed220ea9ae3 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts
@@ -55,11 +55,8 @@ const createTestCases = (spaceId: string) => [
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, namespaces: [ALL_SPACES_ID] },
];
-export default function ({ getService }: FtrProviderContext) {
- const supertest = getService('supertest');
- const esArchiver = getService('esArchiver');
-
- const { addTests, createTestDefinitions } = bulkGetTestSuiteFactory(esArchiver, supertest);
+export default function (context: FtrProviderContext) {
+ const { addTests, createTestDefinitions } = bulkGetTestSuiteFactory(context);
const createTests = (spaceId: string) => {
const testCases = createTestCases(spaceId);
return createTestDefinitions(testCases, false, { singleRequest: true });
diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
index c781eff6d3272..4c5ae878bbf6e 100644
--- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
@@ -14,8 +14,8 @@ import {
} from '@kbn/core/server';
import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
-import { getTestDataLoader } from '../lib/test_data_loader';
-import { FtrProviderContext } from '../ftr_provider_context';
+import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader';
+import type { FtrProviderContext } from '../ftr_provider_context';
type TestResponse = Record;
@@ -74,6 +74,21 @@ const UUID_PATTERN = new RegExp(
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
);
+const SPACE_DATA_TO_LOAD: Array<{ spaceName: string | null; dataUrl: string }> = [
+ {
+ spaceName: null,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json',
+ },
+ {
+ spaceName: SPACE_1.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json',
+ },
+ {
+ spaceName: SPACE_2.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json',
+ },
+];
+
const getDestinationWithoutConflicts = () => 'space_2';
const getDestinationWithConflicts = (originSpaceId?: string) =>
!originSpaceId || originSpaceId === DEFAULT_SPACE_ID ? 'space_1' : DEFAULT_SPACE_ID;
@@ -748,16 +763,19 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) {
// test data only allows for the following spaces as the copy origin
expect(['default', 'space_1']).to.contain(spaceId);
- await testDataLoader.before();
+ await testDataLoader.createFtrSpaces();
});
after(async () => {
- await testDataLoader.after();
+ await testDataLoader.deleteFtrSpaces();
});
describe('single-namespace types', () => {
- beforeEach(async () => await testDataLoader.beforeEach());
- afterEach(async () => await testDataLoader.afterEach());
+ beforeEach(async () => {
+ await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD);
+ });
+
+ afterEach(async () => await testDataLoader.deleteFtrSavedObjectsData());
const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` };
@@ -898,8 +916,8 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) {
const spaces = ['space_2'];
const includeReferences = false;
describe(`multi-namespace types with overwrite=${overwrite} and createNewCopies=${createNewCopies}`, () => {
- before(async () => await testDataLoader.beforeEach());
- after(async () => await testDataLoader.afterEach());
+ before(async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD));
+ after(async () => await testDataLoader.deleteFtrSavedObjectsData());
const testCases = tests.multiNamespaceTestCases(overwrite, createNewCopies);
testCases.forEach(({ testTitle, objects, statusCode, response }) => {
diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
index 58a434bd0ca91..5f2c361714c49 100644
--- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
@@ -11,8 +11,8 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants';
import { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces';
import { getUrlPrefix } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
-import { FtrProviderContext } from '../ftr_provider_context';
-import { getTestDataLoader } from '../lib/test_data_loader';
+import type { FtrProviderContext } from '../ftr_provider_context';
+import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader';
type TestResponse = Record;
@@ -44,6 +44,21 @@ interface ResolveCopyToSpaceTestDefinition {
const NON_EXISTENT_SPACE_ID = 'non_existent_space';
+const SPACE_DATA_TO_LOAD: Array<{ spaceName: string | null; dataUrl: string }> = [
+ {
+ spaceName: null,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json',
+ },
+ {
+ spaceName: SPACE_1.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json',
+ },
+ {
+ spaceName: SPACE_2.id,
+ dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json',
+ },
+];
+
const getDestinationSpace = (originSpaceId?: string) => {
if (!originSpaceId || originSpaceId === DEFAULT_SPACE_ID) {
return 'space_1';
@@ -487,8 +502,10 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) {
});
describe('single-namespace types', () => {
- beforeEach(async () => await testDataLoader.beforeEach());
- afterEach(async () => await testDataLoader.afterEach());
+ beforeEach(
+ async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD)
+ );
+ afterEach(async () => await testDataLoader.deleteFtrSavedObjectsData());
const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` };
const visualizationObject = { type: 'visualization', id: `cts_vis_3_${spaceId}` };
@@ -630,8 +647,8 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) {
const includeReferences = false;
const createNewCopies = false;
describe(`multi-namespace types with "overwrite" retry`, () => {
- before(async () => await testDataLoader.beforeEach());
- after(async () => await testDataLoader.afterEach());
+ before(async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD));
+ after(async () => await testDataLoader.deleteFtrSavedObjectsData());
const testCases = tests.multiNamespaceTestCases();
testCases.forEach(({ testTitle, objects, retries, statusCode, response }) => {
From f5f60b640b935b86ee76e6c808a65b56be93edb1 Mon Sep 17 00:00:00 2001
From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com>
Date: Tue, 4 Oct 2022 18:04:28 +0500
Subject: [PATCH 18/39] [Console] Refactor Console settings toggles to follow
best practices (#140902)
* Refactor settings modal labels
* Fix checks
* Update related test case
* Migrate old settings to new ones
* Refactor migrate fn to be more generic
Co-authored-by: Muhammad Ibragimov
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../application/components/settings_modal.tsx | 28 ++++-----
.../editor/legacy/console_editor/editor.tsx | 4 +-
.../use_send_current_request.test.tsx | 4 +-
.../use_send_current_request.ts | 6 +-
.../console/public/services/settings.ts | 62 ++++++++++++-------
.../translations/translations/fr-FR.json | 2 -
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
8 files changed, 63 insertions(+), 47 deletions(-)
diff --git a/src/plugins/console/public/application/components/settings_modal.tsx b/src/plugins/console/public/application/components/settings_modal.tsx
index 095dde1c29507..67b0e2c0d957a 100644
--- a/src/plugins/console/public/application/components/settings_modal.tsx
+++ b/src/plugins/console/public/application/components/settings_modal.tsx
@@ -77,9 +77,9 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
const [polling, setPolling] = useState(props.settings.polling);
const [pollInterval, setPollInterval] = useState(props.settings.pollInterval);
const [tripleQuotes, setTripleQuotes] = useState(props.settings.tripleQuotes);
- const [isHistoryDisabled, setIsHistoryDisabled] = useState(props.settings.isHistoryDisabled);
- const [isKeyboardShortcutsDisabled, setIsKeyboardShortcutsDisabled] = useState(
- props.settings.isKeyboardShortcutsDisabled
+ const [isHistoryEnabled, setIsHistoryEnabled] = useState(props.settings.isHistoryEnabled);
+ const [isKeyboardShortcutsEnabled, setIsKeyboardShortcutsEnabled] = useState(
+ props.settings.isKeyboardShortcutsEnabled
);
const autoCompleteCheckboxes = [
@@ -140,8 +140,8 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
polling,
pollInterval,
tripleQuotes,
- isHistoryDisabled,
- isKeyboardShortcutsDisabled,
+ isHistoryEnabled,
+ isKeyboardShortcutsEnabled,
});
}
@@ -153,17 +153,17 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
}, []);
const toggleKeyboardShortcuts = useCallback(
- (isDisabled: boolean) => {
+ (isEnabled: boolean) => {
if (props.editorInstance) {
unregisterCommands(props.editorInstance);
- setIsKeyboardShortcutsDisabled(isDisabled);
+ setIsKeyboardShortcutsEnabled(isEnabled);
}
},
[props.editorInstance]
);
const toggleSavingToHistory = useCallback(
- (isDisabled: boolean) => setIsHistoryDisabled(isDisabled),
+ (isEnabled: boolean) => setIsHistoryEnabled(isEnabled),
[]
);
@@ -289,11 +289,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
}
>
}
onChange={(e) => toggleSavingToHistory(e.target.checked)}
@@ -309,11 +309,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
}
>
}
onChange={(e) => toggleKeyboardShortcuts(e.target.checked)}
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 74a052646e198..ed8c87b5df147 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
@@ -259,8 +259,8 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
}, [settings]);
useEffect(() => {
- const { isKeyboardShortcutsDisabled } = settings;
- if (!isKeyboardShortcutsDisabled) {
+ const { isKeyboardShortcutsEnabled } = settings;
+ if (isKeyboardShortcutsEnabled) {
registerCommands({
senseEditor: editorInstanceRef.current!,
sendCurrentRequest,
diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx
index 0c7e4c46d95a6..e895ddc135db8 100644
--- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx
+++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx
@@ -106,7 +106,9 @@ describe('useSendCurrentRequest', () => {
(sendRequest as jest.Mock).mockReturnValue(
[{ request: {} }, { request: {} }] /* two responses to save history */
);
- (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({});
+ (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({
+ isHistoryEnabled: true,
+ });
(mockContextValue.services.history.addToHistory as jest.Mock).mockImplementation(() => {
// Mock throwing
throw new Error('cannot save!');
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 87f72571a63e6..28d875c246ca3 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
@@ -52,9 +52,9 @@ export const useSendCurrentRequest = () => {
const results = await sendRequest({ http, requests });
let saveToHistoryError: undefined | Error;
- const { isHistoryDisabled } = settings.toJSON();
+ const { isHistoryEnabled } = settings.toJSON();
- if (!isHistoryDisabled) {
+ if (isHistoryEnabled) {
results.forEach(({ request: { path, method, data } }) => {
try {
history.addToHistory(path, method, data);
@@ -84,7 +84,7 @@ export const useSendCurrentRequest = () => {
notifications.toasts.remove(toast);
},
onDisableSavingToHistory: () => {
- settings.setIsHistoryDisabled(true);
+ settings.setIsHistoryEnabled(false);
notifications.toasts.remove(toast);
},
}),
diff --git a/src/plugins/console/public/services/settings.ts b/src/plugins/console/public/services/settings.ts
index aa2280f06064f..e4731dd3f3a31 100644
--- a/src/plugins/console/public/services/settings.ts
+++ b/src/plugins/console/public/services/settings.ts
@@ -15,8 +15,8 @@ export const DEFAULT_SETTINGS = Object.freeze({
tripleQuotes: true,
wrapMode: true,
autocomplete: Object.freeze({ fields: true, indices: true, templates: true, dataStreams: true }),
- isHistoryDisabled: false,
- isKeyboardShortcutsDisabled: false,
+ isHistoryEnabled: true,
+ isKeyboardShortcutsEnabled: true,
});
export interface DevToolsSettings {
@@ -31,8 +31,8 @@ export interface DevToolsSettings {
polling: boolean;
pollInterval: number;
tripleQuotes: boolean;
- isHistoryDisabled: boolean;
- isKeyboardShortcutsDisabled: boolean;
+ isHistoryEnabled: boolean;
+ isKeyboardShortcutsEnabled: boolean;
}
enum SettingKeys {
@@ -42,12 +42,32 @@ enum SettingKeys {
AUTOCOMPLETE_SETTINGS = 'autocomplete_settings',
CONSOLE_POLLING = 'console_polling',
POLL_INTERVAL = 'poll_interval',
- IS_HISTORY_DISABLED = 'is_history_disabled',
- IS_KEYBOARD_SHORTCUTS_DISABLED = 'is_keyboard_shortcuts_disabled',
+ IS_HISTORY_ENABLED = 'is_history_enabled',
+ IS_KEYBOARD_SHORTCUTS_ENABLED = 'is_keyboard_shortcuts_enabled',
}
export class Settings {
- constructor(private readonly storage: Storage) {}
+ constructor(private readonly storage: Storage) {
+ // Migration from old settings to new ones
+ this.addMigrationRule('is_history_disabled', SettingKeys.IS_HISTORY_ENABLED, (value: any) => {
+ return !value;
+ });
+ this.addMigrationRule(
+ 'is_keyboard_shortcuts_disabled',
+ SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED,
+ (value: any) => {
+ return !value;
+ }
+ );
+ }
+
+ private addMigrationRule(previousKey: string, newKey: string, migration: (value: any) => any) {
+ const value = this.storage.get(previousKey);
+ if (value !== undefined) {
+ this.storage.set(newKey, migration(value));
+ this.storage.delete(previousKey);
+ }
+ }
getFontSize() {
return this.storage.get(SettingKeys.FONT_SIZE, DEFAULT_SETTINGS.fontSize);
@@ -94,13 +114,13 @@ export class Settings {
return true;
}
- setIsHistoryDisabled(isDisabled: boolean) {
- this.storage.set(SettingKeys.IS_HISTORY_DISABLED, isDisabled);
+ setIsHistoryEnabled(isEnabled: boolean) {
+ this.storage.set(SettingKeys.IS_HISTORY_ENABLED, isEnabled);
return true;
}
- getIsHistoryDisabled() {
- return this.storage.get(SettingKeys.IS_HISTORY_DISABLED, DEFAULT_SETTINGS.isHistoryDisabled);
+ getIsHistoryEnabled() {
+ return this.storage.get(SettingKeys.IS_HISTORY_ENABLED, DEFAULT_SETTINGS.isHistoryEnabled);
}
setPollInterval(interval: number) {
@@ -111,15 +131,15 @@ export class Settings {
return this.storage.get(SettingKeys.POLL_INTERVAL, DEFAULT_SETTINGS.pollInterval);
}
- setIsKeyboardShortcutsDisabled(disable: boolean) {
- this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED, disable);
+ setIsKeyboardShortcutsEnabled(isEnabled: boolean) {
+ this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, isEnabled);
return true;
}
getIsKeyboardShortcutsDisabled() {
return this.storage.get(
- SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED,
- DEFAULT_SETTINGS.isKeyboardShortcutsDisabled
+ SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED,
+ DEFAULT_SETTINGS.isKeyboardShortcutsEnabled
);
}
@@ -131,8 +151,8 @@ export class Settings {
fontSize: parseFloat(this.getFontSize()),
polling: Boolean(this.getPolling()),
pollInterval: this.getPollInterval(),
- isHistoryDisabled: Boolean(this.getIsHistoryDisabled()),
- isKeyboardShortcutsDisabled: Boolean(this.getIsKeyboardShortcutsDisabled()),
+ isHistoryEnabled: Boolean(this.getIsHistoryEnabled()),
+ isKeyboardShortcutsEnabled: Boolean(this.getIsKeyboardShortcutsDisabled()),
};
}
@@ -143,8 +163,8 @@ export class Settings {
autocomplete,
polling,
pollInterval,
- isHistoryDisabled,
- isKeyboardShortcutsDisabled,
+ isHistoryEnabled,
+ isKeyboardShortcutsEnabled,
}: DevToolsSettings) {
this.setFontSize(fontSize);
this.setWrapMode(wrapMode);
@@ -152,8 +172,8 @@ export class Settings {
this.setAutocomplete(autocomplete);
this.setPolling(polling);
this.setPollInterval(pollInterval);
- this.setIsHistoryDisabled(isHistoryDisabled);
- this.setIsKeyboardShortcutsDisabled(isKeyboardShortcutsDisabled);
+ this.setIsHistoryEnabled(isHistoryEnabled);
+ this.setIsKeyboardShortcutsEnabled(isKeyboardShortcutsEnabled);
}
}
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index a5b6ae699c723..006c2744d4eb0 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -260,7 +260,6 @@
"console.settingsPage.autocompleteLabel": "Saisie semi-automatique",
"console.settingsPage.cancelButtonLabel": "Annuler",
"console.settingsPage.dataStreamsLabelText": "Flux de données",
- "console.settingsPage.disableKeyboardShortcutsMessage": "Désactiver les raccourcis clavier",
"console.settingsPage.fieldsLabelText": "Champs",
"console.settingsPage.fontSizeLabel": "Taille de la police",
"console.settingsPage.historyLabel": "Historique",
@@ -274,7 +273,6 @@
"console.settingsPage.refreshInterval.everyHourTimeInterval": "Toutes les heures",
"console.settingsPage.refreshInterval.onceTimeInterval": "Une fois, au chargement de la console",
"console.settingsPage.saveButtonLabel": "Enregistrer",
- "console.settingsPage.savingRequestsToHistoryMessage": "Désactiver l'enregistrement des requêtes dans l'historique",
"console.settingsPage.templatesLabelText": "Modèles",
"console.settingsPage.tripleQuotesMessage": "Utiliser des guillemets triples dans le volet de sortie",
"console.settingsPage.wrapLongLinesLabelText": "Formater les longues lignes",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 72e20dc6efa37..4601cc9bb3919 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -260,7 +260,6 @@
"console.settingsPage.autocompleteLabel": "自動入力",
"console.settingsPage.cancelButtonLabel": "キャンセル",
"console.settingsPage.dataStreamsLabelText": "データストリーム",
- "console.settingsPage.disableKeyboardShortcutsMessage": "キーボードショートカットを無効にする",
"console.settingsPage.fieldsLabelText": "フィールド",
"console.settingsPage.fontSizeLabel": "フォントサイズ",
"console.settingsPage.historyLabel": "履歴",
@@ -274,7 +273,6 @@
"console.settingsPage.refreshInterval.everyHourTimeInterval": "毎時",
"console.settingsPage.refreshInterval.onceTimeInterval": "コンソールの読み込み時に1回",
"console.settingsPage.saveButtonLabel": "保存",
- "console.settingsPage.savingRequestsToHistoryMessage": "履歴へのリクエストの保存を無効にしてください",
"console.settingsPage.templatesLabelText": "テンプレート",
"console.settingsPage.tripleQuotesMessage": "出力ウィンドウでは三重引用符を使用してください",
"console.settingsPage.wrapLongLinesLabelText": "長い行を改行",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index bf90d943ab06b..3181f9602038b 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -260,7 +260,6 @@
"console.settingsPage.autocompleteLabel": "自动完成",
"console.settingsPage.cancelButtonLabel": "取消",
"console.settingsPage.dataStreamsLabelText": "数据流",
- "console.settingsPage.disableKeyboardShortcutsMessage": "禁用键盘快捷键",
"console.settingsPage.fieldsLabelText": "字段",
"console.settingsPage.fontSizeLabel": "字体大小",
"console.settingsPage.historyLabel": "历史记录",
@@ -274,7 +273,6 @@
"console.settingsPage.refreshInterval.everyHourTimeInterval": "每小时",
"console.settingsPage.refreshInterval.onceTimeInterval": "一次,控制台加载时",
"console.settingsPage.saveButtonLabel": "保存",
- "console.settingsPage.savingRequestsToHistoryMessage": "禁止将请求保存到历史记录",
"console.settingsPage.templatesLabelText": "模板",
"console.settingsPage.tripleQuotesMessage": "在输出窗格中使用三重引号",
"console.settingsPage.wrapLongLinesLabelText": "长行换行",
From 890bf7430cd9a62ee1d2f46c16f486bfb6aebd59 Mon Sep 17 00:00:00 2001
From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com>
Date: Tue, 4 Oct 2022 09:09:55 -0400
Subject: [PATCH 19/39] [Security Solution][Analyzer] Make all analyzer apis
have time range as optional (#142536)
---
.../common/endpoint/schema/resolver.ts | 10 ++-
.../e2e/detection_alerts/resolver.cy.ts | 4 +-
.../resolver/data_access_layer/factory.ts | 75 +++++++++----------
.../data_access_layer/mocks/generator_tree.ts | 8 +-
.../mocks/no_ancestors_two_children.ts | 6 +-
..._children_in_index_called_awesome_index.ts | 6 +-
..._children_with_related_events_on_origin.ts | 6 +-
.../one_node_with_paginated_related_events.ts | 6 +-
.../current_related_event_fetcher.ts | 5 +-
.../store/middleware/node_data_fetcher.ts | 4 +-
.../middleware/related_events_fetcher.ts | 4 +-
.../store/middleware/resolver_tree_fetcher.ts | 6 +-
.../public/resolver/types.ts | 8 +-
.../routes/resolver/queries/events.ts | 55 +++-----------
.../routes/resolver/tree/queries/base.ts | 16 ++--
15 files changed, 98 insertions(+), 121 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts
index 15c89c8cd9c28..6de81d3e95a55 100644
--- a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts
@@ -58,10 +58,12 @@ export const validateEvents = {
afterEvent: schema.maybe(schema.string()),
}),
body: schema.object({
- timeRange: schema.object({
- from: schema.string(),
- to: schema.string(),
- }),
+ timeRange: schema.maybe(
+ schema.object({
+ from: schema.string(),
+ to: schema.string(),
+ })
+ ),
indexPatterns: schema.arrayOf(schema.string()),
filter: schema.maybe(schema.string()),
entityType: schema.maybe(schema.string()),
diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts
index aa2263b9b518c..c2436f3f2de9a 100644
--- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts
+++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts
@@ -28,12 +28,12 @@ describe('Analyze events view for alerts', () => {
waitForAlertsToPopulate();
});
- it('should render analyzer when button is clicked', () => {
+ it('should render when button is clicked', () => {
openAnalyzerForFirstAlertInTimeline();
cy.get(ANALYZER_NODE).first().should('be.visible');
});
- it(`should render an analyzer view and display
+ it(`should display
a toast indicating the date range of found events when a time range has 0 events in it`, () => {
const dateContainingZeroEvents = 'Jul 27, 2022 @ 00:00:00.000';
setStartDate(dateContainingZeroEvents);
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts
index 04e694b2cedbb..719fdedb73546 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts
@@ -17,6 +17,17 @@ import type {
ResolverSchema,
} from '../../../common/endpoint/types';
+function getRangeFilter(timeRange: TimeRange | undefined) {
+ return timeRange
+ ? {
+ timeRange: {
+ from: timeRange.from,
+ to: timeRange.to,
+ },
+ }
+ : [];
+}
+
/**
* The data access layer for resolver. All communication with the Kibana server is done through this object. This object is provided to Resolver. In tests, a mock data access layer can be used instead.
*/
@@ -34,7 +45,7 @@ export function dataAccessLayerFactory(
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
const response: ResolverPaginatedEvents = await context.services.http.post(
@@ -43,10 +54,7 @@ export function dataAccessLayerFactory(
query: {},
body: JSON.stringify({
indexPatterns,
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
filter: JSON.stringify({
bool: {
filter: [
@@ -76,16 +84,13 @@ export function dataAccessLayerFactory(
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
const commonFields = {
query: { afterEvent: after, limit: 25 },
body: {
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
indexPatterns,
},
};
@@ -127,30 +132,28 @@ export function dataAccessLayerFactory(
limit,
}: {
ids: string[];
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
limit: number;
}): Promise {
- const response: ResolverPaginatedEvents = await context.services.http.post(
- '/api/endpoint/resolver/events',
- {
- query: { limit },
- body: JSON.stringify({
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
+ const query = {
+ query: { limit },
+ body: JSON.stringify({
+ indexPatterns,
+ ...getRangeFilter(timeRange),
+ filter: JSON.stringify({
+ bool: {
+ filter: [
+ { terms: { 'process.entity_id': ids } },
+ { term: { 'event.category': 'process' } },
+ ],
},
- indexPatterns,
- filter: JSON.stringify({
- bool: {
- filter: [
- { terms: { 'process.entity_id': ids } },
- { term: { 'event.category': 'process' } },
- ],
- },
- }),
}),
- }
+ }),
+ };
+ const response: ResolverPaginatedEvents = await context.services.http.post(
+ '/api/endpoint/resolver/events',
+ query
);
return response.events;
},
@@ -172,7 +175,7 @@ export function dataAccessLayerFactory(
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
/** @description - eventID isn't provided by winlog. This can be removed once runtime fields are available */
@@ -200,10 +203,7 @@ export function dataAccessLayerFactory(
query: { limit: 1 },
body: JSON.stringify({
indexPatterns,
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
filter: JSON.stringify(filter),
}),
}
@@ -217,10 +217,7 @@ export function dataAccessLayerFactory(
query: { limit: 1 },
body: JSON.stringify({
indexPatterns,
- timeRange: {
- from: timeRange.from,
- to: timeRange.to,
- },
+ ...getRangeFilter(timeRange),
entityType: 'alertDetail',
eventID,
}),
@@ -250,7 +247,7 @@ export function dataAccessLayerFactory(
}: {
dataId: string;
schema: ResolverSchema;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indices: string[];
ancestors: number;
descendants: number;
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts
index 130b81c5622b2..6b833c93704b4 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts
@@ -63,7 +63,7 @@ export function generateTreeWithDAL(
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
const node = allNodes.get(entityID);
@@ -88,7 +88,7 @@ export function generateTreeWithDAL(
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
const node = allNodes.get(entityID);
@@ -119,7 +119,7 @@ export function generateTreeWithDAL(
eventCategory: string[];
eventTimestamp: string;
eventID?: string | number;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return null;
@@ -135,7 +135,7 @@ export function generateTreeWithDAL(
limit,
}: {
ids: string[];
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
limit: number;
}): Promise {
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts
index 000d08b4e15c7..e883a96b162e8 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts
@@ -59,7 +59,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return Promise.resolve({
@@ -83,7 +83,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{
events: SafeResolverEvent[];
@@ -110,7 +110,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return null;
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts
index 808c4463f3a89..c4c7fda097e8f 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts
@@ -64,7 +64,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return Promise.resolve({
@@ -90,7 +90,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{
events: SafeResolverEvent[];
@@ -121,7 +121,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return mockEndpointEvent({
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts
index 774111baf165d..30f7e07bf041a 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts
@@ -67,7 +67,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
/**
@@ -97,7 +97,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
const events =
@@ -129,7 +129,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return relatedEvents.events.find((event) => eventModel.eventID(event) === eventID) ?? null;
diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts
index 7eb8c28a433e3..dc7031acdbd91 100644
--- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts
+++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts
@@ -58,7 +58,7 @@ export function oneNodeWithPaginatedEvents(): {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
/**
@@ -86,7 +86,7 @@ export function oneNodeWithPaginatedEvents(): {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> {
let events: SafeResolverEvent[] = [];
@@ -121,7 +121,7 @@ export function oneNodeWithPaginatedEvents(): {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}): Promise {
return mockTree.events.find((event) => eventModel.eventID(event) === eventID) ?? null;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts
index 6b58dd4e8e62e..cd4119f9569e7 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts
@@ -48,8 +48,9 @@ export function CurrentRelatedEventFetcher(
api.dispatch({
type: 'appRequestedCurrentRelatedEventData',
});
- const timeRangeFilters = selectors.timeRangeFilters(state);
-
+ const detectedBounds = selectors.detectedBounds(state);
+ const timeRangeFilters =
+ detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
let result: SafeResolverEvent | null = null;
try {
result = await dataAccessLayer.event({
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts
index c3173b3238737..9a3a9eb3450fd 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts
@@ -60,7 +60,9 @@ export function NodeDataFetcher(
let results: SafeResolverEvent[] | undefined;
try {
- const timeRangeFilters = selectors.timeRangeFilters(state);
+ const detectedBounds = selectors.detectedBounds(state);
+ const timeRangeFilters =
+ detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
results = await dataAccessLayer.nodeData({
ids: Array.from(newIDsToRequest),
timeRange: timeRangeFilters,
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts
index ec0f068b5425c..ab8f71940104e 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts
@@ -30,7 +30,9 @@ export function RelatedEventsFetcher(
const indices = selectors.eventIndices(state);
const oldParams = last;
- const timeRangeFilters = selectors.timeRangeFilters(state);
+ const detectedBounds = selectors.detectedBounds(state);
+ const timeRangeFilters =
+ detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state);
// Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info.
last = newParams;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts
index e4da1af5f4d79..61319158fccc2 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts
@@ -93,9 +93,9 @@ export function ResolverTreeFetcher(
descendants: descendantsRequestAmount(),
});
if (unboundedTree.length > 0) {
- const timestamps = unboundedTree.map((event) =>
- firstNonNullValue(event.data['@timestamp'])
- );
+ const timestamps = unboundedTree
+ .map((event) => firstNonNullValue(event.data['@timestamp']))
+ .sort();
const oldestTimestamp = timestamps[0];
const newestTimestamp = timestamps.slice(-1);
api.dispatch({
diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts
index 00ecd995176eb..88e97f416dc49 100644
--- a/x-pack/plugins/security_solution/public/resolver/types.ts
+++ b/x-pack/plugins/security_solution/public/resolver/types.ts
@@ -692,7 +692,7 @@ export interface DataAccessLayer {
indexPatterns,
}: {
entityID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}) => Promise;
@@ -710,7 +710,7 @@ export interface DataAccessLayer {
entityID: string;
category: string;
after?: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}) => Promise;
@@ -725,7 +725,7 @@ export interface DataAccessLayer {
limit,
}: {
ids: string[];
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
limit: number;
}): Promise;
@@ -747,7 +747,7 @@ export interface DataAccessLayer {
eventTimestamp: string;
eventID?: string | number;
winlogRecordID: string;
- timeRange: TimeRange;
+ timeRange?: TimeRange;
indexPatterns: string[];
}) => Promise;
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts
index ba4f682423670..869ae911ad890 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts
@@ -11,31 +11,22 @@ import type { JsonObject, JsonValue } from '@kbn/utility-types';
import { parseFilterQuery } from '../../../../utils/serialized_query';
import type { SafeResolverEvent } from '../../../../../common/endpoint/types';
import type { PaginationBuilder } from '../utils/pagination';
-
-interface TimeRange {
- from: string;
- to: string;
-}
+import { BaseResolverQuery } from '../tree/queries/base';
+import type { ResolverQueryParams } from '../tree/queries/base';
/**
* Builds a query for retrieving events.
*/
-export class EventsQuery {
- private readonly pagination: PaginationBuilder;
- private readonly indexPatterns: string | string[];
- private readonly timeRange: TimeRange;
+export class EventsQuery extends BaseResolverQuery {
+ readonly pagination: PaginationBuilder;
constructor({
- pagination,
indexPatterns,
timeRange,
- }: {
- pagination: PaginationBuilder;
- indexPatterns: string | string[];
- timeRange: TimeRange;
- }) {
+ isInternalRequest,
+ pagination,
+ }: ResolverQueryParams & { pagination: PaginationBuilder }) {
+ super({ indexPatterns, timeRange, isInternalRequest });
this.pagination = pagination;
- this.indexPatterns = indexPatterns;
- this.timeRange = timeRange;
}
private query(filters: JsonObject[]): JsonObject {
@@ -44,15 +35,7 @@ export class EventsQuery {
bool: {
filter: [
...filters,
- {
- range: {
- '@timestamp': {
- gte: this.timeRange.from,
- lte: this.timeRange.to,
- format: 'strict_date_optional_time',
- },
- },
- },
+ ...this.getRangeFilter(),
{
term: { 'event.kind': 'event' },
},
@@ -71,15 +54,7 @@ export class EventsQuery {
{
term: { 'event.id': id },
},
- {
- range: {
- '@timestamp': {
- gte: this.timeRange.from,
- lte: this.timeRange.to,
- format: 'strict_date_optional_time',
- },
- },
- },
+ ...this.getRangeFilter(),
],
},
},
@@ -97,15 +72,7 @@ export class EventsQuery {
{
term: { 'process.entity_id': id },
},
- {
- range: {
- '@timestamp': {
- gte: this.timeRange.from,
- lte: this.timeRange.to,
- format: 'strict_date_optional_time',
- },
- },
- },
+ ...this.getRangeFilter(),
],
},
},
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts
index 6637e7931b056..256f2b58b6864 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts
@@ -11,10 +11,10 @@ import type { TimeRange } from '../utils';
import { resolverFields } from '../utils';
export interface ResolverQueryParams {
- readonly schema: ResolverSchema;
+ readonly schema?: ResolverSchema;
readonly indexPatterns: string | string[];
readonly timeRange: TimeRange | undefined;
- readonly isInternalRequest: boolean;
+ readonly isInternalRequest?: boolean;
readonly resolverFields?: JsonValue[];
getRangeFilter?: () => Array<{
range: { '@timestamp': { gte: string; lte: string; format: string } };
@@ -25,12 +25,18 @@ export class BaseResolverQuery implements ResolverQueryParams {
readonly schema: ResolverSchema;
readonly indexPatterns: string | string[];
readonly timeRange: TimeRange | undefined;
- readonly isInternalRequest: boolean;
+ readonly isInternalRequest?: boolean;
readonly resolverFields?: JsonValue[];
constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) {
- this.resolverFields = resolverFields(schema);
- this.schema = schema;
+ const schemaOrDefault = schema
+ ? schema
+ : {
+ id: 'process.entity_id',
+ parent: 'process.parent.entity_id',
+ };
+ this.resolverFields = resolverFields(schemaOrDefault);
+ this.schema = schemaOrDefault;
this.indexPatterns = indexPatterns;
this.timeRange = timeRange;
this.isInternalRequest = isInternalRequest;
From f147fe8b98ae94558411fd43d822c329c688a33a Mon Sep 17 00:00:00 2001
From: Josh Dover <1813008+joshdover@users.noreply.github.com>
Date: Tue, 4 Oct 2022 15:28:15 +0200
Subject: [PATCH 20/39] [Fleet] Update unenroll logic to account for new API
key fields (#142579)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/fleet/common/types/models/agent.ts | 5 +++++
.../fleet/server/services/agents/unenroll.test.ts | 13 +++++++++++++
.../services/agents/unenroll_action_runner.ts | 10 ++++++++++
3 files changed, 28 insertions(+)
diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts
index fa54f8c943e27..ea2ad78dc10cd 100644
--- a/x-pack/plugins/fleet/common/types/models/agent.ts
+++ b/x-pack/plugins/fleet/common/types/models/agent.ts
@@ -94,7 +94,12 @@ interface AgentBase {
export interface Agent extends AgentBase {
id: string;
access_api_key?: string;
+ // @deprecated
default_api_key_history?: FleetServerAgent['default_api_key_history'];
+ outputs?: Array<{
+ api_key_id: string;
+ to_retire_api_key_ids?: FleetServerAgent['default_api_key_history'];
+ }>;
status?: AgentStatus;
packages: string[];
sort?: Array;
diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts
index 5beb5c0a9ac00..9169df19fbcfb 100644
--- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts
+++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts
@@ -331,6 +331,15 @@ describe('invalidateAPIKeysForAgents', () => {
id: 'defaultApiKeyHistory2',
},
],
+ outputs: [
+ {
+ api_key_id: 'outputApiKey1',
+ to_retire_api_key_ids: [{ id: 'outputApiKeyRetire1' }, { id: 'outputApiKeyRetire2' }],
+ },
+ {
+ api_key_id: 'outputApiKey2',
+ },
+ ],
} as any,
]);
@@ -340,6 +349,10 @@ describe('invalidateAPIKeysForAgents', () => {
'defaultApiKey1',
'defaultApiKeyHistory1',
'defaultApiKeyHistory2',
+ 'outputApiKey1',
+ 'outputApiKeyRetire1',
+ 'outputApiKeyRetire2',
+ 'outputApiKey2',
]);
});
});
diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts
index c735254f18256..fed5d44fe98e8 100644
--- a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts
+++ b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts
@@ -215,6 +215,16 @@ export async function invalidateAPIKeysForAgents(agents: Agent[]) {
if (agent.default_api_key_history) {
agent.default_api_key_history.forEach((apiKey) => keys.push(apiKey.id));
}
+ if (agent.outputs) {
+ agent.outputs.forEach((output) => {
+ if (output.api_key_id) {
+ keys.push(output.api_key_id);
+ }
+ if (output.to_retire_api_key_ids) {
+ output.to_retire_api_key_ids.forEach((apiKey) => keys.push(apiKey.id));
+ }
+ });
+ }
return keys;
}, []);
From 5add1f9b76d1a60eea521bea880aec84c594a72d Mon Sep 17 00:00:00 2001
From: Philippe Oberti
Date: Tue, 4 Oct 2022 08:45:11 -0500
Subject: [PATCH 21/39] [TIP] Add full screen feature for indicators table
(#142519)
[TIP] Add full screen feature for indicators table
---
.../use_toolbar_options.test.tsx.snap | 120 ++++++++++++++++++
.../hooks/use_toolbar_options.test.tsx | 119 +----------------
.../hooks/use_toolbar_options.tsx | 2 +-
3 files changed, 124 insertions(+), 117 deletions(-)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap
new file mode 100644
index 0000000000000..4b58689023333
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap
@@ -0,0 +1,120 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`useToolbarOptions() should return correct value for 0 indicators total 1`] = `
+Object {
+ "additionalControls": Object {
+ "left": Object {
+ "append": ,
+ "prepend":
+
+ -
+
+ ,
+ },
+ "right": ,
+ },
+ "showDisplaySelector": false,
+ "showFullScreenSelector": true,
+}
+`;
+
+exports[`useToolbarOptions() should return correct value for 25 indicators total 1`] = `
+Object {
+ "additionalControls": Object {
+ "left": Object {
+ "append": ,
+ "prepend":
+
+ Showing
+ 1
+ -
+ 25
+ of
+
+ 25
+ indicators
+
+ ,
+ },
+ "right": ,
+ },
+ "showDisplaySelector": false,
+ "showFullScreenSelector": true,
+}
+`;
+
+exports[`useToolbarOptions() should return correct value for 50 indicators total 1`] = `
+Object {
+ "additionalControls": Object {
+ "left": Object {
+ "append": ,
+ "prepend":
+
+ Showing
+ 26
+ -
+ 50
+ of
+
+ 50
+ indicators
+
+ ,
+ },
+ "right": ,
+ },
+ "showDisplaySelector": false,
+ "showFullScreenSelector": true,
+}
+`;
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx
index 084279fe8353a..ecf1cbf0a477a 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx
@@ -25,40 +25,7 @@ describe('useToolbarOptions()', () => {
{ wrapper: TestProvidersComponent }
);
- expect(result.result.current).toMatchInlineSnapshot(`
- Object {
- "additionalControls": Object {
- "left": Object {
- "append": ,
- "prepend":
-
- -
-
- ,
- },
- "right": ,
- },
- "showDisplaySelector": false,
- "showFullScreenSelector": false,
- }
- `);
+ expect(result.result.current).toMatchSnapshot();
});
it('should return correct value for 25 indicators total', () => {
@@ -76,47 +43,7 @@ describe('useToolbarOptions()', () => {
{ wrapper: TestProvidersComponent }
);
- expect(result.result.current).toMatchInlineSnapshot(`
- Object {
- "additionalControls": Object {
- "left": Object {
- "append": ,
- "prepend":
-
- Showing
- 1
- -
- 25
- of
-
- 25
- indicators
-
- ,
- },
- "right": ,
- },
- "showDisplaySelector": false,
- "showFullScreenSelector": false,
- }
- `);
+ expect(result.result.current).toMatchSnapshot();
});
it('should return correct value for 50 indicators total', () => {
@@ -134,46 +61,6 @@ describe('useToolbarOptions()', () => {
{ wrapper: TestProvidersComponent }
);
- expect(result.result.current).toMatchInlineSnapshot(`
- Object {
- "additionalControls": Object {
- "left": Object {
- "append": ,
- "prepend":
-
- Showing
- 26
- -
- 50
- of
-
- 50
- indicators
-
- ,
- },
- "right": ,
- },
- "showDisplaySelector": false,
- "showFullScreenSelector": false,
- }
- `);
+ expect(result.result.current).toMatchSnapshot();
});
});
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx
index b19d6df71463e..12bd94951e33c 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx
@@ -41,7 +41,7 @@ export const useToolbarOptions = ({
return useMemo(
() => ({
showDisplaySelector: false,
- showFullScreenSelector: false,
+ showFullScreenSelector: true,
additionalControls: {
left: {
prepend: (
From 796dcb99153f3872c2f220cf0687620425731db2 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Tue, 4 Oct 2022 07:58:43 -0600
Subject: [PATCH 22/39] skip failing test suite (#142548)
---
.../instrumented_events/from_the_browser/loaded_dashboard.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts
index bc04d60c3fb54..7b21a5637d167 100644
--- a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts
+++ b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts
@@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const dashboardAddPanel = getService('dashboardAddPanel');
const queryBar = getService('queryBar');
- describe('Loaded Dashboard', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/142548
+ describe.skip('Loaded Dashboard', () => {
let fromTimestamp: string | undefined;
const getEvents = async (count: number, options?: GetEventsOptions) =>
From b798f9f627c780cebea2221db38dbb5db8a5e089 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 4 Oct 2022 16:23:42 +0200
Subject: [PATCH 23/39] [Uptime] Unskip flaky api test (#142595)
---
x-pack/test/api_integration/apis/uptime/feature_controls.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/x-pack/test/api_integration/apis/uptime/feature_controls.ts b/x-pack/test/api_integration/apis/uptime/feature_controls.ts
index 8185cb0f03a20..39d7406636353 100644
--- a/x-pack/test/api_integration/apis/uptime/feature_controls.ts
+++ b/x-pack/test/api_integration/apis/uptime/feature_controls.ts
@@ -145,8 +145,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
}
});
- // FLAKY: https://github.com/elastic/kibana/issues/136542
- describe.skip('spaces', () => {
+ describe('spaces', () => {
// the following tests create a user_1 which has uptime read access to space_1 and dashboard all access to space_2
const space1Id = 'space_1';
const space2Id = 'space_2';
From d95e690e9e633c5cff79c6f9b847a8bb6fb16b5f Mon Sep 17 00:00:00 2001
From: jennypavlova
Date: Tue, 4 Oct 2022 16:37:19 +0200
Subject: [PATCH 24/39] [Infrastructure UI] Use same no data messaging on hosts
view (#142063)
* [WIP] Implement No Data message
* Implement refetch
* Render lens component and hide when there is no data
* Add onLoading hook and conditional rendering
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../metrics/hosts/components/hosts_table.tsx | 35 ++++++++++++++++++-
.../pages/metrics/hosts/hosts_content.tsx | 19 ++++++++++
2 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
index 74f2468eb4c45..e92ac801e8612 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx
@@ -8,8 +8,10 @@
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import type { Query, TimeRange } from '@kbn/es-query';
-import React from 'react';
+import React, { useState } from 'react';
import type { DataView } from '@kbn/data-views-plugin/public';
+import { i18n } from '@kbn/i18n';
+import { NoData } from '../../../../components/empty_states';
import { InfraClientStartDeps } from '../../../../types';
const getLensHostsTable = (
@@ -498,23 +500,54 @@ interface Props {
timeRange: TimeRange;
query: Query;
searchSessionId: string;
+ onRefetch: () => void;
+ onLoading: (isLoading: boolean) => void;
+ isLensLoading: boolean;
}
export const HostsTable: React.FunctionComponent = ({
dataView,
timeRange,
query,
searchSessionId,
+ onRefetch,
+ onLoading,
+ isLensLoading,
}) => {
const {
services: { lens },
} = useKibana();
const LensComponent = lens?.EmbeddableComponent;
+ const [noData, setNoData] = useState(false);
+
+ if (noData && !isLensLoading) {
+ return (
+
+ );
+ }
return (
{
+ if (!isLoading && adapters?.tables) {
+ setNoData(adapters?.tables.tables.default?.rows.length === 0);
+ onLoading(false);
+ }
+ }}
/>
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx
index 63e95a19f1c7b..7bf087db39eb5 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx
@@ -27,6 +27,7 @@ export const HostsContent: React.FunctionComponent = () => {
useMetricsDataViewContext();
// needed to refresh the lens table when filters havent changed
const [searchSessionId, setSearchSessionId] = useState(data.search.session.start());
+ const [isLensLoading, setIsLensLoading] = useState(false);
const onQuerySubmit = useCallback(
(payload: { dateRange: TimeRange; query?: Query }) => {
@@ -34,11 +35,26 @@ export const HostsContent: React.FunctionComponent = () => {
if (payload.query) {
setQuery(payload.query);
}
+ setIsLensLoading(true);
setSearchSessionId(data.search.session.start());
},
[setDateRange, setQuery, data.search.session]
);
+ const onLoading = useCallback(
+ (isLoading: boolean) => {
+ if (isLensLoading) {
+ setIsLensLoading(isLoading);
+ }
+ },
+ [setIsLensLoading, isLensLoading]
+ );
+
+ const onRefetch = useCallback(() => {
+ setIsLensLoading(true);
+ setSearchSessionId(data.search.session.start());
+ }, [data.search.session]);
+
return (
{metricsDataView ? (
@@ -61,6 +77,9 @@ export const HostsContent: React.FunctionComponent = () => {
timeRange={dateRange}
query={query}
searchSessionId={searchSessionId}
+ onRefetch={onRefetch}
+ onLoading={onLoading}
+ isLensLoading={isLensLoading}
/>
>
) : hasFailedCreatingDataView || hasFailedFetchingDataView ? (
From 001d44cb028df385afe7ff01d5ea3ca5e432efed Mon Sep 17 00:00:00 2001
From: Luke Gmys
Date: Tue, 4 Oct 2022 16:41:56 +0200
Subject: [PATCH 25/39] [TIP] Add update status component (#142560)
---
.../cypress/e2e/indicators.cy.ts | 4 +-
.../cypress/screens/indicators.ts | 2 +-
.../public/components/layout/layout.tsx | 16 ++++-
.../public/components/update_status/index.ts | 8 +++
.../update_status/update_status.test.tsx | 63 +++++++++++++++++++
.../update_status/update_status.tsx | 43 +++++++++++++
.../indicators/hooks/use_indicators.test.tsx | 1 +
.../indicators/hooks/use_indicators.ts | 5 +-
.../indicators/indicators_page.test.tsx | 1 +
.../modules/indicators/indicators_page.tsx | 8 ++-
10 files changed, 143 insertions(+), 8 deletions(-)
create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/index.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx
create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx
diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts
index e52effa09ab3b..c5d67894aa0ff 100644
--- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts
+++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts
@@ -185,9 +185,7 @@ describe('Indicators', () => {
it('should render the inspector flyout', () => {
cy.get(INSPECTOR_BUTTON).last().click({ force: true });
- cy.get(INSPECTOR_PANEL).should('be.visible');
-
- cy.get(INSPECTOR_PANEL).contains('Index patterns');
+ cy.get(INSPECTOR_PANEL).contains('Indicators search requests');
});
});
});
diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
index 2bc1b704e8159..0464e57c6749b 100644
--- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
+++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts
@@ -31,7 +31,7 @@ export const FILTERS_GLOBAL_CONTAINER = '[data-test-subj="filters-global-contain
export const TIME_RANGE_PICKER = `[data-test-subj="superDatePickerToggleQuickMenuButton"]`;
-export const QUERY_INPUT = `[data-test-subj="iocListPageQueryInput"]`;
+export const QUERY_INPUT = `[data-test-subj="queryInput"]`;
export const EMPTY_STATE = '[data-test-subj="indicatorsTableEmptyState"]';
diff --git a/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx b/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx
index 6c7621977b8dc..04ee12819d988 100644
--- a/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx
+++ b/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx
@@ -6,17 +6,23 @@
*/
import { EuiPageHeader, EuiPageHeaderSection, EuiSpacer, EuiText } from '@elastic/eui';
-import React, { FC } from 'react';
+import React, { FC, ReactNode } from 'react';
import { SecuritySolutionPageWrapper } from '../../containers/security_solution_page_wrapper';
export interface LayoutProps {
pageTitle?: string;
border?: boolean;
+ subHeader?: ReactNode;
}
export const TITLE_TEST_ID = 'tiDefaultPageLayoutTitle';
-export const DefaultPageLayout: FC = ({ children, pageTitle, border = true }) => {
+export const DefaultPageLayout: FC = ({
+ children,
+ pageTitle,
+ border = true,
+ subHeader,
+}) => {
return (
@@ -26,6 +32,12 @@ export const DefaultPageLayout: FC = ({ children, pageTitle, border
{pageTitle}
)}
+ {subHeader ? (
+ <>
+
+ {subHeader}
+ >
+ ) : null}
diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts b/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts
new file mode 100644
index 0000000000000..f83c0e64fda23
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './update_status';
diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx
new file mode 100644
index 0000000000000..2ed1503d89a78
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { render } from '@testing-library/react';
+import React from 'react';
+import { TestProvidersComponent } from '../../common/mocks/test_providers';
+import { UpdateStatus } from './update_status';
+
+describe('', () => {
+ it('should render Updated now', () => {
+ const result = render(, {
+ wrapper: TestProvidersComponent,
+ });
+
+ expect(result.asFragment()).toMatchInlineSnapshot(`
+
+
+
+ `);
+ });
+
+ it('should render Updating when isUpdating', () => {
+ const result = render(, {
+ wrapper: TestProvidersComponent,
+ });
+
+ expect(result.asFragment()).toMatchInlineSnapshot(`
+
+
+
+ `);
+ });
+});
diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx
new file mode 100644
index 0000000000000..02f43481186dd
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedRelative } from '@kbn/i18n-react';
+
+interface UpdateStatusProps {
+ updatedAt: number;
+ isUpdating: boolean;
+}
+
+const UPDATING = i18n.translate('xpack.threatIntelligence.updateStatus.updating', {
+ defaultMessage: 'Updating...',
+});
+
+const UPDATED = i18n.translate('xpack.threatIntelligence.updateStatus.updated', {
+ defaultMessage: 'Updated',
+});
+
+export const UpdateStatus: React.FC = ({ isUpdating, updatedAt }) => (
+
+
+
+ {isUpdating ? (
+ UPDATING
+ ) : (
+ <>
+ {UPDATED}
+
+
+ >
+ )}
+
+
+
+);
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
index 42f6a4eb1fdb7..40d64636fa346 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
@@ -105,6 +105,7 @@ describe('useIndicators()', () => {
expect(hookResult.result.current).toMatchInlineSnapshot(`
Object {
+ "dataUpdatedAt": 0,
"handleRefresh": [Function],
"indicatorCount": 0,
"indicators": Array [],
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts
index 2352f302a1d4d..e2e0aaddf07aa 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts
@@ -47,6 +47,8 @@ export interface UseIndicatorsValue {
* Data loading is in progress (see docs on `isFetching` here: https://tanstack.com/query/v4/docs/guides/queries)
*/
isFetching: boolean;
+
+ dataUpdatedAt: number;
}
export const useIndicators = ({
@@ -95,7 +97,7 @@ export const useIndicators = ({
[inspectorAdapters, searchService]
);
- const { isLoading, isFetching, data, refetch } = useQuery(
+ const { isLoading, isFetching, data, refetch, dataUpdatedAt } = useQuery(
[
'indicatorsTable',
{
@@ -132,5 +134,6 @@ export const useIndicators = ({
isLoading,
isFetching,
handleRefresh,
+ dataUpdatedAt,
};
};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
index e46c605d1a90a..7f4db9fa75262 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
@@ -42,6 +42,7 @@ describe('', () => {
onChangeItemsPerPage: stub,
onChangePage: stub,
handleRefresh: stub,
+ dataUpdatedAt: Date.now(),
});
(useFilters as jest.MockedFunction).mockReturnValue({
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
index 511faaa73a7a0..fcf690631d740 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
@@ -20,6 +20,7 @@ import { useColumnSettings } from './components/indicators_table/hooks/use_colum
import { useAggregatedIndicators } from './hooks/use_aggregated_indicators';
import { IndicatorsFilters } from './containers/indicators_filters';
import { useSecurityContext } from '../../hooks/use_security_context';
+import { UpdateStatus } from '../../components/update_status';
const queryClient = new QueryClient();
@@ -48,6 +49,7 @@ const IndicatorsPageContent: VFC = () => {
pagination,
isLoading: isLoadingIndicators,
isFetching: isFetchingIndicators,
+ dataUpdatedAt,
} = useIndicators({
filters,
filterQuery,
@@ -72,10 +74,14 @@ const IndicatorsPageContent: VFC = () => {
return (
-
+ }
+ >
+
Date: Tue, 4 Oct 2022 15:51:26 +0100
Subject: [PATCH 26/39] Add getByTestSubj command (#142591)
---
.../power_user/feature_flag/comparison.cy.ts | 12 ++---
.../integration_policy.cy.ts | 12 ++---
.../settings/agent_configurations.cy.ts | 11 ++--
.../power_user/settings/custom_links.cy.ts | 10 ++--
.../storage_explorer/storage_explorer.cy.ts | 14 ++---
.../e2e/read_only_user/deep_links.cy.ts | 8 +--
.../e2e/read_only_user/dependencies.cy.ts | 6 +--
.../read_only_user/errors/error_details.cy.ts | 4 +-
.../read_only_user/errors/errors_page.cy.ts | 6 +--
.../cypress/e2e/read_only_user/home.cy.ts | 2 +-
.../header_filters/header_filters.cy.ts | 2 +-
.../service_inventory/service_inventory.cy.ts | 4 +-
.../service_overview/errors_table.cy.ts | 8 +--
.../service_overview/header_filters.cy.ts | 28 ++++------
.../service_overview/instances_table.cy.ts | 16 ++----
.../service_overview/service_overview.cy.ts | 42 +++++++--------
.../service_overview/time_comparison.cy.ts | 40 ++++++--------
.../transaction_details/span_links.cy.ts | 52 +++++++++----------
.../transaction_details.cy.ts | 12 ++---
.../transactions_overview.cy.ts | 8 +--
.../apm/ftr_e2e/cypress/support/commands.ts | 18 ++++---
.../apm/ftr_e2e/cypress/support/types.d.ts | 1 +
22 files changed, 144 insertions(+), 172 deletions(-)
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts
index d1159efd0fc90..7d40105db192e 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts
@@ -36,19 +36,19 @@ describe('Comparison feature flag', () => {
it('shows the comparison feature enabled in services overview', () => {
cy.visitKibana('/app/apm/services');
cy.get('input[type="checkbox"]#comparison').should('be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('not.be.disabled');
});
it('shows the comparison feature enabled in dependencies overview', () => {
cy.visitKibana('/app/apm/dependencies');
cy.get('input[type="checkbox"]#comparison').should('be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('not.be.disabled');
});
it('shows the comparison feature disabled in service map overview page', () => {
cy.visitKibana('/app/apm/service-map');
cy.get('input[type="checkbox"]#comparison').should('be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('not.be.disabled');
});
});
@@ -71,7 +71,7 @@ describe('Comparison feature flag', () => {
it('shows the comparison feature disabled in services overview', () => {
cy.visitKibana('/app/apm/services');
cy.get('input[type="checkbox"]#comparison').should('not.be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
it('shows the comparison feature disabled in dependencies overview page', () => {
@@ -81,13 +81,13 @@ describe('Comparison feature flag', () => {
cy.visitKibana('/app/apm/dependencies');
cy.wait('@topDependenciesRequest');
cy.get('input[type="checkbox"]#comparison').should('not.be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
it('shows the comparison feature disabled in service map overview page', () => {
cy.visitKibana('/app/apm/service-map');
cy.get('input[type="checkbox"]#comparison').should('not.be.checked');
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts
index c25e6a6800311..5d275770e462d 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts
@@ -60,21 +60,19 @@ describe('when navigating to integration page', () => {
cy.visitKibana(integrationsPath);
// open integration policy form
- cy.get('[data-test-subj="integration-card:epr:apm:featured').click();
+ cy.getByTestSubj('integration-card:epr:apm:featured').click();
cy.contains('Elastic APM in Fleet').click();
cy.contains('a', 'APM integration').click();
- cy.get('[data-test-subj="addIntegrationPolicyButton"]').click();
+ cy.getByTestSubj('addIntegrationPolicyButton').click();
});
it('checks validators for required fields', () => {
const requiredFields = policyFormFields.filter((field) => field.required);
requiredFields.map((field) => {
- cy.get(`[data-test-subj="${field.selector}"`).clear();
- cy.get('[data-test-subj="createPackagePolicySaveButton"').should(
- 'be.disabled'
- );
- cy.get(`[data-test-subj="${field.selector}"`).type(field.value);
+ cy.getByTestSubj(field.selector).clear();
+ cy.getByTestSubj('createPackagePolicySaveButton').should('be.disabled');
+ cy.getByTestSubj(field.selector).type(field.value);
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts
index 5be39b4f082dc..47f8c537b100c 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts
@@ -90,7 +90,7 @@ describe('Agent configuration', () => {
'/api/apm/settings/agent-configuration/environments?*'
).as('serviceEnvironmentApi');
cy.contains('Create configuration').click();
- cy.get('[data-test-subj="serviceNameComboBox"]')
+ cy.getByTestSubj('serviceNameComboBox')
.click()
.type('opbeans-node')
.type('{enter}');
@@ -98,7 +98,7 @@ describe('Agent configuration', () => {
cy.contains('opbeans-node').realClick();
cy.wait('@serviceEnvironmentApi');
- cy.get('[data-test-subj="serviceEnviromentComboBox"]')
+ cy.getByTestSubj('serviceEnviromentComboBox')
.click({ force: true })
.type('prod')
.type('{enter}');
@@ -115,14 +115,11 @@ describe('Agent configuration', () => {
'/api/apm/settings/agent-configuration/environments'
).as('serviceEnvironmentApi');
cy.contains('Create configuration').click();
- cy.get('[data-test-subj="serviceNameComboBox"]')
- .click()
- .type('All')
- .type('{enter}');
+ cy.getByTestSubj('serviceNameComboBox').click().type('All').type('{enter}');
cy.contains('All').realClick();
cy.wait('@serviceEnvironmentApi');
- cy.get('[data-test-subj="serviceEnviromentComboBox"]')
+ cy.getByTestSubj('serviceEnviromentComboBox')
.click({ force: true })
.type('All');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts
index 615ff2b49a85a..b680f745609bc 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts
@@ -52,7 +52,7 @@ describe('Custom links', () => {
it('creates custom link', () => {
cy.visitKibana(basePath);
- const emptyPrompt = cy.get('[data-test-subj="customLinksEmptyPrompt"]');
+ const emptyPrompt = cy.getByTestSubj('customLinksEmptyPrompt');
cy.contains('Create custom link').click();
cy.contains('Create link');
cy.contains('Save').should('be.disabled');
@@ -63,7 +63,7 @@ describe('Custom links', () => {
emptyPrompt.should('not.exist');
cy.contains('foo');
cy.contains('https://foo.com');
- cy.get('[data-test-subj="editCustomLink"]').click();
+ cy.getByTestSubj('editCustomLink').click();
cy.contains('Delete').click();
});
@@ -71,14 +71,14 @@ describe('Custom links', () => {
cy.visitKibana(basePath);
// wait for empty prompt
- cy.get('[data-test-subj="customLinksEmptyPrompt"]').should('be.visible');
+ cy.getByTestSubj('customLinksEmptyPrompt').should('be.visible');
cy.contains('Create custom link').click();
- cy.get('[data-test-subj="filter-0"]').select('service.name');
+ cy.getByTestSubj('filter-0').select('service.name');
cy.get(
'[data-test-subj="service.name.value"] [data-test-subj="comboBoxSearchInput"]'
).type('foo');
- cy.get('[data-test-subj="filter-0"]').select('service.environment');
+ cy.getByTestSubj('filter-0').select('service.environment');
cy.get(
'[data-test-subj="service.environment.value"] [data-test-subj="comboBoxInput"]'
).should('not.contain', 'foo');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
index e989ea5cf0faf..20577f8bf5793 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
@@ -85,7 +85,7 @@ describe('Storage Explorer', () => {
});
it('renders the storage timeseries chart', () => {
- cy.get('[data-test-subj="storageExplorerTimeseriesChart"]');
+ cy.getByTestSubj('storageExplorerTimeseriesChart');
});
it('has a list of services and environments', () => {
@@ -115,7 +115,7 @@ describe('Storage Explorer', () => {
it('with the correct environment when changing the environment', () => {
cy.wait(mainAliasNames);
- cy.get('[data-test-subj="environmentFilter"]').type('production');
+ cy.getByTestSubj('environmentFilter').type('production');
cy.contains('button', 'production').click({ force: true });
@@ -148,7 +148,7 @@ describe('Storage Explorer', () => {
it('with the correct lifecycle phase when changing the lifecycle phase', () => {
cy.wait(mainAliasNames);
- cy.get('[data-test-subj="storageExplorerLifecyclePhaseSelect"]').click();
+ cy.getByTestSubj('storageExplorerLifecyclePhaseSelect').click();
cy.contains('button', 'Warm').click();
cy.expectAPIsToHaveBeenCalledWith({
@@ -180,13 +180,13 @@ describe('Storage Explorer', () => {
cy.wait(mainAliasNames);
cy.contains('opbeans-node');
- cy.get('[data-test-subj="storageDetailsButton_opbeans-node"]').click();
- cy.get('[data-test-subj="loadingSpinner"]').should('be.visible');
+ cy.getByTestSubj('storageDetailsButton_opbeans-node').click();
+ cy.getByTestSubj('loadingSpinner').should('be.visible');
cy.wait('@storageDetailsRequest');
cy.contains('Service storage details');
- cy.get('[data-test-subj="storageExplorerTimeseriesChart"]');
- cy.get('[data-test-subj="serviceStorageDetailsTable"]');
+ cy.getByTestSubj('storageExplorerTimeseriesChart');
+ cy.getByTestSubj('serviceStorageDetailsTable');
});
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
index cfcabe85b5b2a..00b842f3265c7 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
@@ -11,7 +11,7 @@ describe('APM deep links', () => {
});
it('navigates to apm links on search elastic', () => {
cy.visitKibana('/');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
cy.contains('APM');
cy.contains('APM / Services');
cy.contains('APM / Traces');
@@ -23,17 +23,17 @@ describe('APM deep links', () => {
cy.contains('APM').click({ force: true });
cy.url().should('include', '/apm/services');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
// navigates to services page
cy.contains('APM / Services').click({ force: true });
cy.url().should('include', '/apm/services');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
// navigates to traces page
cy.contains('APM / Traces').click({ force: true });
cy.url().should('include', '/apm/traces');
- cy.get('[data-test-subj="nav-search-input"]').type('APM');
+ cy.getByTestSubj('nav-search-input').type('APM');
// navigates to service maps
cy.contains('APM / Service Map').click({ force: true });
cy.url().should('include', '/apm/service-map');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts
index 653809a8e04d3..2ef3ae42b1aac 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts
@@ -66,9 +66,9 @@ describe('Dependencies', () => {
})}`
);
- cy.get('[data-test-subj="latencyChart"]');
- cy.get('[data-test-subj="throughputChart"]');
- cy.get('[data-test-subj="errorRateChart"]');
+ cy.getByTestSubj('latencyChart');
+ cy.getByTestSubj('throughputChart');
+ cy.getByTestSubj('errorRateChart');
cy.contains('opbeans-java').click({ force: true });
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts
index 19de523c7ab1f..d00d8036df3bb 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts
@@ -68,13 +68,13 @@ describe('Error details', () => {
it('shows errors distribution chart', () => {
cy.visitKibana(errorDetailsPageHref);
cy.contains('Error group 00000');
- cy.get('[data-test-subj="errorDistribution"]').contains('Occurrences');
+ cy.getByTestSubj('errorDistribution').contains('Occurrences');
});
it('shows top erroneous transactions table', () => {
cy.visitKibana(errorDetailsPageHref);
cy.contains('Top 5 affected transactions');
- cy.get('[data-test-subj="topErroneousTransactionsTable"]')
+ cy.getByTestSubj('topErroneousTransactionsTable')
.contains('a', 'GET /apple 🍎')
.click();
cy.url().should('include', 'opbeans-java/transactions/view');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
index 301b3384ee2eb..8ac95d509d0bd 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
@@ -81,14 +81,14 @@ describe('Errors page', () => {
it('clicking on type adds a filter in the kuerybar', () => {
cy.visitKibana(javaServiceErrorsPageHref);
- cy.get('[data-test-subj="headerFilterKuerybar"]')
+ cy.getByTestSubj('headerFilterKuerybar')
.invoke('val')
.should('be.empty');
// `force: true` because Cypress says the element is 0x0
cy.contains('exception 0').click({
force: true,
});
- cy.get('[data-test-subj="headerFilterKuerybar"]')
+ cy.getByTestSubj('headerFilterKuerybar')
.its('length')
.should('be.gt', 0);
cy.get('table')
@@ -158,7 +158,7 @@ describe('Check detailed statistics API with multiple errors', () => {
])
);
});
- cy.get('[data-test-subj="pagination-button-1"]').click();
+ cy.getByTestSubj('pagination-button-1').click();
cy.wait('@errorsDetailedStatistics').then((payload) => {
expect(payload.request.body.groupIds).eql(
JSON.stringify([
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts
index 2ee2f4f019b12..e0c4a3aedd2b3 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts
@@ -69,7 +69,7 @@ describe('Home page', () => {
cy.contains('Services');
cy.contains('opbeans-rum').click({ force: true });
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'page-load'
);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
index c4e87ac15fbe1..4f72e968d81f8 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
@@ -44,7 +44,7 @@ describe('Service inventory - header filters', () => {
cy.contains('Services');
cy.contains('opbeans-node');
cy.contains('service 1');
- cy.get('[data-test-subj="headerFilterKuerybar"]')
+ cy.getByTestSubj('headerFilterKuerybar')
.type(`service.name: "${specialServiceName}"`)
.type('{enter}');
cy.contains('service 1');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
index 015df91d792e9..2d40c690a8c92 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
@@ -93,7 +93,7 @@ describe('Service inventory', () => {
it('with the correct environment when changing the environment', () => {
cy.wait(mainAliasNames);
- cy.get('[data-test-subj="environmentFilter"]').type('production');
+ cy.getByTestSubj('environmentFilter').type('production');
cy.contains('button', 'production').click();
@@ -175,7 +175,7 @@ describe('Service inventory', () => {
])
);
});
- cy.get('[data-test-subj="pagination-button-1"]').click();
+ cy.getByTestSubj('pagination-button-1').click();
cy.wait('@detailedStatisticsRequest').then((payload) => {
expect(payload.request.body.serviceNames).eql(
JSON.stringify([
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
index b175eb0430ed4..d693148010c7e 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
@@ -50,16 +50,12 @@ describe('Errors table', () => {
it('clicking on type adds a filter in the kuerybar and navigates to errors page', () => {
cy.visitKibana(serviceOverviewHref);
- cy.get('[data-test-subj="headerFilterKuerybar"]')
- .invoke('val')
- .should('be.empty');
+ cy.getByTestSubj('headerFilterKuerybar').invoke('val').should('be.empty');
// `force: true` because Cypress says the element is 0x0
cy.contains('Exception').click({
force: true,
});
- cy.get('[data-test-subj="headerFilterKuerybar"]')
- .its('length')
- .should('be.gt', 0);
+ cy.getByTestSubj('headerFilterKuerybar').its('length').should('be.gt', 0);
cy.get('table').find('td:contains("Exception")').should('have.length', 1);
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
index 6376d544821aa..8a25024506696 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
@@ -77,13 +77,13 @@ describe('Service overview - header filters', () => {
cy.visitKibana(serviceOverviewHref);
cy.contains('opbeans-node');
cy.url().should('not.include', 'transactionType');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
cy.url().should('include', 'transactionType=Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -94,7 +94,7 @@ describe('Service overview - header filters', () => {
cy.intercept('GET', endpoint).as(name);
});
cy.visitKibana(serviceOverviewHref);
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
@@ -104,9 +104,9 @@ describe('Service overview - header filters', () => {
value: 'transactionType=request',
});
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
cy.url().should('include', 'transactionType=Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -129,18 +129,12 @@ describe('Service overview - header filters', () => {
})
);
cy.contains('opbeans-java');
- cy.get('[data-test-subj="headerFilterKuerybar"]').type('transaction.n');
+ cy.getByTestSubj('headerFilterKuerybar').type('transaction.n');
cy.contains('transaction.name');
- cy.get('[data-test-subj="suggestionContainer"]')
- .find('li')
- .first()
- .click();
- cy.get('[data-test-subj="headerFilterKuerybar"]').type(':');
- cy.get('[data-test-subj="suggestionContainer"]')
- .find('li')
- .first()
- .click();
- cy.get('[data-test-subj="headerFilterKuerybar"]').type('{enter}');
+ cy.getByTestSubj('suggestionContainer').find('li').first().click();
+ cy.getByTestSubj('headerFilterKuerybar').type(':');
+ cy.getByTestSubj('suggestionContainer').find('li').first().click();
+ cy.getByTestSubj('headerFilterKuerybar').type('{enter}');
cy.url().should('include', '&kuery=transaction.name');
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts
index 03653df2b0bb6..578b116a10592 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts
@@ -63,7 +63,7 @@ describe('Instances table', () => {
it('shows empty message', () => {
cy.visitKibana(testServiveHref);
cy.contains('test-service');
- cy.get('[data-test-subj="serviceInstancesTableContainer"]').contains(
+ cy.getByTestSubj('serviceInstancesTableContainer').contains(
'No instances found'
);
});
@@ -77,9 +77,7 @@ describe('Instances table', () => {
it('hides instances table', () => {
cy.visitKibana(serviceRumOverviewHref);
cy.contains('opbeans-rum');
- cy.get('[data-test-subj="serviceInstancesTableContainer"]').should(
- 'not.exist'
- );
+ cy.getByTestSubj('serviceInstancesTableContainer').should('not.exist');
});
});
@@ -109,10 +107,8 @@ describe('Instances table', () => {
cy.contains(serviceNodeName);
cy.wait('@instancesDetailsRequest');
- cy.get(
- `[data-test-subj="instanceDetailsButton_${serviceNodeName}"]`
- ).realClick();
- cy.get('[data-test-subj="loadingSpinner"]').should('be.visible');
+ cy.getByTestSubj(`instanceDetailsButton_${serviceNodeName}`).realClick();
+ cy.getByTestSubj('loadingSpinner').should('be.visible');
cy.wait('@instanceDetailsRequest').then(() => {
cy.contains('Service');
});
@@ -130,9 +126,7 @@ describe('Instances table', () => {
cy.contains(serviceNodeName);
cy.wait('@instancesDetailsRequest');
- cy.get(
- `[data-test-subj="instanceActionsButton_${serviceNodeName}"]`
- ).click();
+ cy.getByTestSubj(`instanceActionsButton_${serviceNodeName}`).click();
cy.contains('Pod logs');
cy.contains('Pod metrics');
// cy.contains('Container logs');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts
index e8319c8efafeb..8173e94557b29 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts
@@ -109,13 +109,13 @@ describe('Service Overview', () => {
cy.contains('opbeans-node');
// set skipFailures to true to not fail the test when there are accessibility failures
checkA11y({ skipFailures: true });
- cy.get('[data-test-subj="latencyChart"]');
- cy.get('[data-test-subj="throughput"]');
- cy.get('[data-test-subj="transactionsGroupTable"]');
- cy.get('[data-test-subj="serviceOverviewErrorsTable"]');
- cy.get('[data-test-subj="dependenciesTable"]');
- cy.get('[data-test-subj="instancesLatencyDistribution"]');
- cy.get('[data-test-subj="serviceOverviewInstancesTable"]');
+ cy.getByTestSubj('latencyChart');
+ cy.getByTestSubj('throughput');
+ cy.getByTestSubj('transactionsGroupTable');
+ cy.getByTestSubj('serviceOverviewErrorsTable');
+ cy.getByTestSubj('dependenciesTable');
+ cy.getByTestSubj('instancesLatencyDistribution');
+ cy.getByTestSubj('serviceOverviewInstancesTable');
});
});
@@ -134,17 +134,17 @@ describe('Service Overview', () => {
cy.wait('@transactionTypesRequest');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
cy.contains('Transactions').click();
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -159,18 +159,18 @@ describe('Service Overview', () => {
cy.visitKibana(baseUrl);
cy.wait('@transactionTypesRequest');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
cy.contains('View transactions').click();
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
@@ -226,7 +226,7 @@ describe('Service Overview', () => {
'suggestionsRequest'
);
- cy.get('[data-test-subj="environmentFilter"] input').type('production', {
+ cy.getByTestSubj('environmentFilter').find('input').type('production', {
force: true,
});
@@ -235,9 +235,7 @@ describe('Service Overview', () => {
value: 'fieldValue=production',
});
- cy.get(
- '[data-test-subj="comboBoxOptionsList environmentFilter-optionsList"]'
- )
+ cy.getByTestSubj('comboBoxOptionsList environmentFilter-optionsList')
.contains('production')
.click({ force: true });
@@ -271,11 +269,11 @@ describe('Service Overview', () => {
});
it('when selecting a different comparison window', () => {
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
// selects another comparison type
- cy.get('[data-test-subj="comparisonSelect"]').select('1w');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w');
+ cy.getByTestSubj('comparisonSelect').select('1w');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1w');
cy.expectAPIsToHaveBeenCalledWith({
apisIntercepted: aliasNamesWithComparison,
value: 'offset',
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
index 718a2a4a06cf7..bce3da42d5a3f 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
@@ -101,18 +101,18 @@ describe('Service overview: Time Comparison', () => {
cy.visitKibana(serviceOverviewPath);
cy.contains('opbeans-java');
// opens the page with "Day before" selected
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
// selects another comparison type
- cy.get('[data-test-subj="comparisonSelect"]').select('1w');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w');
+ cy.getByTestSubj('comparisonSelect').select('1w');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1w');
});
it('changes comparison type when a new time range is selected', () => {
cy.visitKibana(serviceOverviewHref);
cy.contains('opbeans-java');
// Time comparison default value
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
cy.contains('Day before');
cy.contains('Week before');
@@ -121,17 +121,14 @@ describe('Service overview: Time Comparison', () => {
'2021-10-20T00:00:00.000Z'
);
- cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click();
+ cy.getByTestSubj('superDatePickerApplyTimeButton').click();
- cy.get('[data-test-subj="comparisonSelect"]').should(
- 'have.value',
- '864000000ms'
- );
- cy.get('[data-test-subj="comparisonSelect"]').should(
+ cy.getByTestSubj('comparisonSelect').should('have.value', '864000000ms');
+ cy.getByTestSubj('comparisonSelect').should(
'not.contain.text',
'Day before'
);
- cy.get('[data-test-subj="comparisonSelect"]').should(
+ cy.getByTestSubj('comparisonSelect').should(
'not.contain.text',
'Week before'
);
@@ -141,17 +138,14 @@ describe('Service overview: Time Comparison', () => {
cy.contains('Week before');
cy.changeTimeRange('Last 24 hours');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d');
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1d');
cy.contains('Day before');
cy.contains('Week before');
cy.changeTimeRange('Last 7 days');
- cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w');
- cy.get('[data-test-subj="comparisonSelect"]').should(
- 'contain.text',
- 'Week before'
- );
- cy.get('[data-test-subj="comparisonSelect"]').should(
+ cy.getByTestSubj('comparisonSelect').should('have.value', '1w');
+ cy.getByTestSubj('comparisonSelect').should('contain.text', 'Week before');
+ cy.getByTestSubj('comparisonSelect').should(
'not.contain.text',
'Day before'
);
@@ -170,7 +164,7 @@ describe('Service overview: Time Comparison', () => {
);
cy.contains('opbeans-java');
cy.wait('@throughputChartRequest');
- cy.get('[data-test-subj="throughput"]')
+ cy.getByTestSubj('throughput')
.get('#echHighlighterClipPath__throughput')
.realHover({ position: 'center' });
cy.contains('Week before');
@@ -186,17 +180,17 @@ describe('Service overview: Time Comparison', () => {
cy.contains('opbeans-java');
// Comparison is enabled by default
- cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled');
+ cy.getByTestSubj('comparisonSelect').should('be.enabled');
// toggles off comparison
cy.contains('Comparison').click();
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
});
it('calls APIs without comparison time range', () => {
cy.visitKibana(serviceOverviewHref);
- cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled');
+ cy.getByTestSubj('comparisonSelect').should('be.enabled');
const offset = `offset=1d`;
// When the page loads it fetches all APIs with comparison time range
@@ -212,7 +206,7 @@ describe('Service overview: Time Comparison', () => {
// toggles off comparison
cy.contains('Comparison').click();
- cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled');
+ cy.getByTestSubj('comparisonSelect').should('be.disabled');
// When comparison is disabled APIs are called withou comparison time range
cy.wait(apisToIntercept.map(({ name }) => `@${name}`)).then(
(interceptions) => {
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
index cddba048e8a18..60b36b10ee4a3 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
@@ -50,8 +50,8 @@ describe('Span links', () => {
);
cy.contains('Transaction A').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('2 incoming');
@@ -64,8 +64,8 @@ describe('Span links', () => {
);
cy.contains('Transaction B').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('1 incoming');
@@ -78,8 +78,8 @@ describe('Span links', () => {
);
cy.contains('Transaction C').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.transactionCId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerConsumerIds.transactionCId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('1 incoming');
@@ -92,8 +92,8 @@ describe('Span links', () => {
);
cy.contains('Transaction C').click();
cy.contains('1 Span link');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.spanCId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerConsumerIds.spanCId}`
).realHover();
cy.contains('1 Span link found');
cy.contains('1 incoming');
@@ -106,8 +106,8 @@ describe('Span links', () => {
);
cy.contains('Transaction D').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.transactionDId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerMultipleIds.transactionDId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('0 incoming');
@@ -120,8 +120,8 @@ describe('Span links', () => {
);
cy.contains('Transaction D').click();
cy.contains('2 Span links');
- cy.get(
- `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.spanEId}"]`
+ cy.getByTestSubj(
+ `spanLinksBadge_${ids.producerMultipleIds.spanEId}`
).realHover();
cy.contains('2 Span links found');
cy.contains('0 incoming');
@@ -136,7 +136,7 @@ describe('Span links', () => {
);
cy.contains('Transaction A').click();
cy.contains('Span A').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('producer-consumer')
.should('have.attr', 'href')
.and('include', '/services/producer-consumer/overview');
@@ -155,7 +155,7 @@ describe('Span links', () => {
'include',
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Outgoing links (0)'
);
@@ -167,7 +167,7 @@ describe('Span links', () => {
);
cy.contains('Transaction B').click();
cy.contains('Span B').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('consumer-multiple')
.should('have.attr', 'href')
@@ -178,9 +178,7 @@ describe('Span links', () => {
'include',
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').select(
- 'Outgoing links (1)'
- );
+ cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)');
cy.contains('Unknown');
cy.contains('trace#1-span#1');
});
@@ -193,7 +191,7 @@ describe('Span links', () => {
cy.get(
`[aria-controls="${ids.producerConsumerIds.transactionCId}"]`
).click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('consumer-multiple')
.should('have.attr', 'href')
@@ -205,9 +203,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').select(
- 'Outgoing links (1)'
- );
+ cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)');
cy.contains('producer-internal-only')
.should('have.attr', 'href')
.and('include', '/services/producer-internal-only/overview');
@@ -225,7 +221,7 @@ describe('Span links', () => {
);
cy.contains('Transaction C').click();
cy.contains('Span C').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('consumer-multiple')
.should('have.attr', 'href')
@@ -237,7 +233,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Outgoing links (0)'
);
@@ -251,7 +247,7 @@ describe('Span links', () => {
cy.get(
`[aria-controls="${ids.producerMultipleIds.transactionDId}"]`
).click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('producer-consumer')
.should('have.attr', 'href')
@@ -273,7 +269,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerInternalOnlyIds.transactionAId}?waterfallItemId=${ids.producerInternalOnlyIds.spanAId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Incoming links (0)'
);
@@ -285,7 +281,7 @@ describe('Span links', () => {
);
cy.contains('Transaction D').click();
cy.contains('Span E').click();
- cy.get('[data-test-subj="spanLinksTab"]').click();
+ cy.getByTestSubj('spanLinksTab').click();
cy.contains('producer-external-only')
.should('have.attr', 'href')
@@ -307,7 +303,7 @@ describe('Span links', () => {
`link-to/transaction/${ids.producerConsumerIds.transactionCId}?waterfallItemId=${ids.producerConsumerIds.transactionCId}`
);
- cy.get('[data-test-subj="spanLinkTypeSelect"]').should(
+ cy.getByTestSubj('spanLinkTypeSelect').should(
'contain.text',
'Incoming links (0)'
);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts
index 5172a5f167fc9..09bd37f5b0b6c 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts
@@ -42,15 +42,15 @@ describe('Transaction details', () => {
it('shows transaction name and transaction charts', () => {
cy.contains('h2', 'GET /api/product');
- cy.get('[data-test-subj="latencyChart"]');
- cy.get('[data-test-subj="throughput"]');
- cy.get('[data-test-subj="transactionBreakdownChart"]');
- cy.get('[data-test-subj="errorRate"]');
+ cy.getByTestSubj('latencyChart');
+ cy.getByTestSubj('throughput');
+ cy.getByTestSubj('transactionBreakdownChart');
+ cy.getByTestSubj('errorRate');
});
it('shows top errors table', () => {
cy.contains('Top 5 errors');
- cy.get('[data-test-subj="topErrorsForTransactionTable"]')
+ cy.getByTestSubj('topErrorsForTransactionTable')
.contains('a', '[MockError] Foo')
.click();
cy.url().should('include', 'opbeans-java/errors');
@@ -58,7 +58,7 @@ describe('Transaction details', () => {
describe('when navigating to a trace sample', () => {
it('keeps the same trace sample after reloading the page', () => {
- cy.get('[data-test-subj="pagination-button-last"]').click();
+ cy.getByTestSubj('pagination-button-last').click();
cy.url().then((url) => {
cy.reload();
cy.url().should('eq', url);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts
index 83753b7fe2595..2e7e0d336cd5d 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts
@@ -49,17 +49,17 @@ describe('Transactions Overview', () => {
it('persists transaction type selected when navigating to Overview tab', () => {
cy.visitKibana(serviceTransactionsHref);
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'request'
);
- cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker');
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').select('Worker');
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
cy.get('a[href*="/app/apm/services/opbeans-node/overview"]').click();
- cy.get('[data-test-subj="headerFilterTransactionType"]').should(
+ cy.getByTestSubj('headerFilterTransactionType').should(
'have.value',
'Worker'
);
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
index 7830e791c3655..9e6e0189e636c 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
@@ -52,15 +52,19 @@ Cypress.Commands.add(
}
);
+Cypress.Commands.add('getByTestSubj', (selector: string) => {
+ return cy.get(`[data-test-subj="${selector}"]`);
+});
+
Cypress.Commands.add('changeTimeRange', (value: string) => {
- cy.get('[data-test-subj="superDatePickerToggleQuickMenuButton"]').click();
+ cy.getByTestSubj('superDatePickerToggleQuickMenuButton').click();
cy.contains(value).click();
});
Cypress.Commands.add('visitKibana', (url: string) => {
cy.visit(url);
- cy.get('[data-test-subj="kbnLoadingMessage"]').should('exist');
- cy.get('[data-test-subj="kbnLoadingMessage"]').should('not.exist', {
+ cy.getByTestSubj('kbnLoadingMessage').should('exist');
+ cy.getByTestSubj('kbnLoadingMessage').should('not.exist', {
timeout: 50000,
});
});
@@ -70,13 +74,13 @@ Cypress.Commands.add(
(start: string, end: string) => {
const format = 'MMM D, YYYY @ HH:mm:ss.SSS';
- cy.get('[data-test-subj="superDatePickerstartDatePopoverButton"]').click();
- cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]')
+ cy.getByTestSubj('superDatePickerstartDatePopoverButton').click();
+ cy.getByTestSubj('superDatePickerAbsoluteDateInput')
.eq(0)
.clear({ force: true })
.type(moment(start).format(format), { force: true });
- cy.get('[data-test-subj="superDatePickerendDatePopoverButton"]').click();
- cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]')
+ cy.getByTestSubj('superDatePickerendDatePopoverButton').click();
+ cy.getByTestSubj('superDatePickerAbsoluteDateInput')
.eq(1)
.clear({ force: true })
.type(moment(end).format(format), { force: true });
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
index 2235847e584a4..5d59d4691820a 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
@@ -22,5 +22,6 @@ declare namespace Cypress {
value: string;
}): void;
updateAdvancedSettings(settings: Record): void;
+ getByTestSubj(selector: string): Chainable>;
}
}
From d7700a609ff6d720b33c477c62f84038eb3e5021 Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 4 Oct 2022 11:01:21 -0400
Subject: [PATCH 27/39] Removing link from native connector advanced
configuration steps (#142541)
---
.../native_connector_advanced_configuration.tsx | 16 ++--------------
1 file changed, 2 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
index fba38e958163a..3e3582bb619fa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
@@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { generateEncodedPath } from '../../../../../shared/encode_path_params';
-import { EuiLinkTo, EuiButtonTo } from '../../../../../shared/react_router_helpers';
+import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
import { SEARCH_INDEX_TAB_PATH } from '../../../../routes';
import { IndexNameLogic } from '../../index_name_logic';
@@ -31,19 +31,7 @@ export const NativeConnectorAdvancedConfiguration: React.FC = () => {
- {i18n.translate(
- 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.recurringScheduleLinkLabel',
- {
- defaultMessage: 'recurring sync schedule',
- }
- )}
-
- ),
- }}
+ defaultMessage="Finalize your connector by triggering a one time sync, or setting a recurring sync schedule."
/>
From 53bf927a6fe28545d5b8d7513591f3d8100f8b30 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Tue, 4 Oct 2022 11:03:39 -0400
Subject: [PATCH 28/39] [Fleet] Bulk install packages before creating agent and
package policy (#142471)
---
.../hooks/devtools_request.tsx | 74 ++++
.../single_page_layout/hooks/form.tsx | 318 ++++++++++++++
.../single_page_layout/hooks/index.tsx | 9 +
.../single_page_layout/index.tsx | 414 ++++--------------
.../fleet/public/hooks/use_request/epm.ts | 10 +
5 files changed, 487 insertions(+), 338 deletions(-)
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
new file mode 100644
index 0000000000000..55e91154060b7
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 { useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import { ExperimentalFeaturesService } from '../../../../../services';
+import {
+ generateCreatePackagePolicyDevToolsRequest,
+ generateCreateAgentPolicyDevToolsRequest,
+} from '../../../services';
+import {
+ FLEET_SYSTEM_PACKAGE,
+ HIDDEN_API_REFERENCE_PACKAGES,
+} from '../../../../../../../../common/constants';
+import type { PackageInfo, NewAgentPolicy, NewPackagePolicy } from '../../../../../types';
+import { SelectedPolicyTab } from '../../components';
+
+export function useDevToolsRequest({
+ newAgentPolicy,
+ packagePolicy,
+ packageInfo,
+ selectedPolicyTab,
+ withSysMonitoring,
+}: {
+ withSysMonitoring: boolean;
+ selectedPolicyTab: SelectedPolicyTab;
+ newAgentPolicy: NewAgentPolicy;
+ packagePolicy: NewPackagePolicy;
+ packageInfo?: PackageInfo;
+}) {
+ const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } =
+ ExperimentalFeaturesService.get();
+
+ const showDevtoolsRequest =
+ !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') &&
+ isShowDevtoolRequestExperimentEnabled;
+
+ const [devtoolRequest, devtoolRequestDescription] = useMemo(() => {
+ if (selectedPolicyTab === SelectedPolicyTab.NEW) {
+ const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
+ return [
+ `${generateCreateAgentPolicyDevToolsRequest(
+ newAgentPolicy,
+ withSysMonitoring && !packagePolicyIsSystem
+ )}\n\n${generateCreatePackagePolicyDevToolsRequest({
+ ...packagePolicy,
+ })}`,
+ i18n.translate(
+ 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription',
+ {
+ defaultMessage:
+ 'These Kibana requests creates a new agent policy and a new package policy.',
+ }
+ ),
+ ];
+ }
+
+ return [
+ generateCreatePackagePolicyDevToolsRequest({
+ ...packagePolicy,
+ }),
+ i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', {
+ defaultMessage: 'This Kibana request creates a new package policy.',
+ }),
+ ];
+ }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]);
+
+ return { showDevtoolsRequest, devtoolRequest, devtoolRequestDescription };
+}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
new file mode 100644
index 0000000000000..e0f206ef612a8
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
@@ -0,0 +1,318 @@
+/*
+ * 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 { useCallback, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { safeLoad } from 'js-yaml';
+
+import type {
+ AgentPolicy,
+ NewPackagePolicy,
+ NewAgentPolicy,
+ CreatePackagePolicyRequest,
+ PackagePolicy,
+ PackageInfo,
+} from '../../../../../types';
+import {
+ useStartServices,
+ sendCreateAgentPolicy,
+ sendCreatePackagePolicy,
+ sendBulkInstallPackages,
+} from '../../../../../hooks';
+import { isVerificationError } from '../../../../../services';
+import { FLEET_ELASTIC_AGENT_PACKAGE, FLEET_SYSTEM_PACKAGE } from '../../../../../../../../common';
+import { useConfirmForceInstall } from '../../../../../../integrations/hooks';
+import { validatePackagePolicy, validationHasErrors } from '../../services';
+import type { PackagePolicyValidationResults } from '../../services';
+import type { PackagePolicyFormState } from '../../types';
+import { SelectedPolicyTab } from '../../components';
+import { useOnSaveNavigate } from '../../hooks';
+
+async function createAgentPolicy({
+ packagePolicy,
+ newAgentPolicy,
+ withSysMonitoring,
+}: {
+ packagePolicy: NewPackagePolicy;
+ newAgentPolicy: NewAgentPolicy;
+ withSysMonitoring: boolean;
+}): Promise {
+ // do not create agent policy with system integration if package policy already is for system package
+ const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
+ const resp = await sendCreateAgentPolicy(newAgentPolicy, {
+ withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem,
+ });
+ if (resp.error) {
+ throw resp.error;
+ }
+ if (!resp.data) {
+ throw new Error('Invalid agent policy creation no data');
+ }
+ return resp.data.item;
+}
+
+async function savePackagePolicy(pkgPolicy: CreatePackagePolicyRequest['body']) {
+ const result = await sendCreatePackagePolicy(pkgPolicy);
+
+ return result;
+}
+
+export function useOnSubmit({
+ agentCount,
+ selectedPolicyTab,
+ newAgentPolicy,
+ withSysMonitoring,
+ queryParamsPolicyId,
+ packageInfo,
+}: {
+ packageInfo?: PackageInfo;
+ newAgentPolicy: NewAgentPolicy;
+ withSysMonitoring: boolean;
+ selectedPolicyTab: SelectedPolicyTab;
+ agentCount: number;
+ queryParamsPolicyId: string | undefined;
+}) {
+ const { notifications } = useStartServices();
+ const confirmForceInstall = useConfirmForceInstall();
+ // only used to store the resulting package policy once saved
+ const [savedPackagePolicy, setSavedPackagePolicy] = useState();
+ // Form state
+ const [formState, setFormState] = useState('VALID');
+
+ const [agentPolicy, setAgentPolicy] = useState();
+ // New package policy state
+ const [packagePolicy, setPackagePolicy] = useState({
+ name: '',
+ description: '',
+ namespace: 'default',
+ policy_id: '',
+ enabled: true,
+ inputs: [],
+ });
+
+ // Validation state
+ const [validationResults, setValidationResults] = useState();
+ const [hasAgentPolicyError, setHasAgentPolicyError] = useState(false);
+ const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
+
+ // Update agent policy method
+ const updateAgentPolicy = useCallback(
+ (updatedAgentPolicy: AgentPolicy | undefined) => {
+ if (updatedAgentPolicy) {
+ setAgentPolicy(updatedAgentPolicy);
+ if (packageInfo) {
+ setHasAgentPolicyError(false);
+ }
+ } else {
+ setHasAgentPolicyError(true);
+ setAgentPolicy(undefined);
+ }
+
+ // eslint-disable-next-line no-console
+ console.debug('Agent policy updated', updatedAgentPolicy);
+ },
+ [packageInfo, setAgentPolicy]
+ );
+ // Update package policy validation
+ const updatePackagePolicyValidation = useCallback(
+ (newPackagePolicy?: NewPackagePolicy) => {
+ if (packageInfo) {
+ const newValidationResult = validatePackagePolicy(
+ newPackagePolicy || packagePolicy,
+ packageInfo,
+ safeLoad
+ );
+ setValidationResults(newValidationResult);
+ // eslint-disable-next-line no-console
+ console.debug('Package policy validation results', newValidationResult);
+
+ return newValidationResult;
+ }
+ },
+ [packagePolicy, packageInfo]
+ );
+ // Update package policy method
+ const updatePackagePolicy = useCallback(
+ (updatedFields: Partial) => {
+ const newPackagePolicy = {
+ ...packagePolicy,
+ ...updatedFields,
+ };
+ setPackagePolicy(newPackagePolicy);
+
+ // eslint-disable-next-line no-console
+ console.debug('Package policy updated', newPackagePolicy);
+ const newValidationResults = updatePackagePolicyValidation(newPackagePolicy);
+ const hasPackage = newPackagePolicy.package;
+ const hasValidationErrors = newValidationResults
+ ? validationHasErrors(newValidationResults)
+ : false;
+ const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== '';
+ if (
+ hasPackage &&
+ (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) &&
+ !hasValidationErrors
+ ) {
+ setFormState('VALID');
+ } else {
+ setFormState('INVALID');
+ }
+ },
+ [packagePolicy, setFormState, updatePackagePolicyValidation, selectedPolicyTab]
+ );
+
+ const onSaveNavigate = useOnSaveNavigate({
+ packagePolicy,
+ queryParamsPolicyId,
+ });
+
+ const navigateAddAgent = (policy?: PackagePolicy) =>
+ onSaveNavigate(policy, ['openEnrollmentFlyout']);
+
+ const navigateAddAgentHelp = (policy?: PackagePolicy) =>
+ onSaveNavigate(policy, ['showAddAgentHelp']);
+
+ const onSubmit = useCallback(
+ async ({
+ force,
+ overrideCreatedAgentPolicy,
+ }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => {
+ if (formState === 'VALID' && hasErrors) {
+ setFormState('INVALID');
+ return;
+ }
+ if (agentCount !== 0 && formState !== 'CONFIRM') {
+ setFormState('CONFIRM');
+ return;
+ }
+ let createdPolicy = overrideCreatedAgentPolicy;
+ if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) {
+ try {
+ setFormState('LOADING');
+ if ((withSysMonitoring || newAgentPolicy.monitoring_enabled?.length) ?? 0 > 0) {
+ const packagesToPreinstall: string[] = [];
+ if (packageInfo) {
+ packagesToPreinstall.push(packageInfo.name);
+ }
+ if (withSysMonitoring) {
+ packagesToPreinstall.push(FLEET_SYSTEM_PACKAGE);
+ }
+ if (newAgentPolicy.monitoring_enabled?.length ?? 0 > 0) {
+ packagesToPreinstall.push(FLEET_ELASTIC_AGENT_PACKAGE);
+ }
+
+ if (packagesToPreinstall.length > 0) {
+ await sendBulkInstallPackages([...new Set(packagesToPreinstall)]);
+ }
+ }
+
+ createdPolicy = await createAgentPolicy({
+ newAgentPolicy,
+ packagePolicy,
+ withSysMonitoring,
+ });
+ setAgentPolicy(createdPolicy);
+ updatePackagePolicy({ policy_id: createdPolicy.id });
+ } catch (e) {
+ setFormState('VALID');
+ notifications.toasts.addError(e, {
+ title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', {
+ defaultMessage: 'Unable to create agent policy',
+ }),
+ });
+ return;
+ }
+ }
+
+ setFormState('LOADING');
+ // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately
+ const { error, data } = await savePackagePolicy({
+ ...packagePolicy,
+ policy_id: createdPolicy?.id ?? packagePolicy.policy_id,
+ force,
+ });
+ setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS');
+ if (!error) {
+ setSavedPackagePolicy(data!.item);
+
+ const hasAgentsAssigned = agentCount && agentPolicy;
+ if (!hasAgentsAssigned) {
+ setFormState('SUBMITTED_NO_AGENTS');
+ return;
+ }
+ onSaveNavigate(data!.item);
+
+ notifications.toasts.addSuccess({
+ title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', {
+ defaultMessage: `'{packagePolicyName}' integration added.`,
+ values: {
+ packagePolicyName: packagePolicy.name,
+ },
+ }),
+ text: hasAgentsAssigned
+ ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
+ defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
+ values: {
+ agentPolicyName: agentPolicy!.name,
+ },
+ })
+ : undefined,
+ 'data-test-subj': 'packagePolicyCreateSuccessToast',
+ });
+ } else {
+ if (isVerificationError(error)) {
+ setFormState('VALID'); // don't show the add agent modal
+ const forceInstall = await confirmForceInstall(packagePolicy.package!);
+
+ if (forceInstall) {
+ // skip creating the agent policy because it will have already been successfully created
+ onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true });
+ }
+ return;
+ }
+ notifications.toasts.addError(error, {
+ title: 'Error',
+ });
+ setFormState('VALID');
+ }
+ },
+ [
+ formState,
+ hasErrors,
+ agentCount,
+ selectedPolicyTab,
+ packagePolicy,
+ notifications.toasts,
+ agentPolicy,
+ onSaveNavigate,
+ confirmForceInstall,
+ newAgentPolicy,
+ updatePackagePolicy,
+ withSysMonitoring,
+ packageInfo,
+ ]
+ );
+
+ return {
+ agentPolicy,
+ updateAgentPolicy,
+ packagePolicy,
+ updatePackagePolicy,
+ savedPackagePolicy,
+ onSubmit,
+ formState,
+ setFormState,
+ hasErrors,
+ validationResults,
+ setValidationResults,
+ hasAgentPolicyError,
+ setHasAgentPolicyError,
+ // TODO check
+ navigateAddAgent,
+ navigateAddAgentHelp,
+ };
+}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx
new file mode 100644
index 0000000000000..33d1cee841590
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { useDevToolsRequest } from './devtools_request';
+export { useOnSubmit } from './form';
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
index fae3c84f21268..02f36e2cadcfe 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
@@ -21,35 +21,16 @@ import {
EuiErrorBoundary,
} from '@elastic/eui';
import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
-import { safeLoad } from 'js-yaml';
-import { useCancelAddPackagePolicy, useOnSaveNavigate } from '../hooks';
-import type { CreatePackagePolicyRequest } from '../../../../../../../common/types';
+import { useCancelAddPackagePolicy } from '../hooks';
import { splitPkgKey } from '../../../../../../../common/services';
-import {
- dataTypes,
- FLEET_SYSTEM_PACKAGE,
- HIDDEN_API_REFERENCE_PACKAGES,
-} from '../../../../../../../common/constants';
-import { useConfirmForceInstall } from '../../../../../integrations/hooks';
-import type {
- AgentPolicy,
- NewAgentPolicy,
- NewPackagePolicy,
- PackagePolicy,
-} from '../../../../types';
-import {
- sendCreatePackagePolicy,
- useStartServices,
- useConfig,
- sendGetAgentStatus,
- useGetPackageInfoByKey,
- sendCreateAgentPolicy,
-} from '../../../../hooks';
+import { dataTypes } from '../../../../../../../common/constants';
+import type { NewAgentPolicy } from '../../../../types';
+import { useConfig, sendGetAgentStatus, useGetPackageInfoByKey } from '../../../../hooks';
import {
Loading,
- Error,
+ Error as ErrorComponent,
ExtensionWrapper,
DevtoolsRequestFlyoutButton,
} from '../../../../components';
@@ -57,34 +38,21 @@ import {
import { agentPolicyFormValidation, ConfirmDeployAgentPolicyModal } from '../../components';
import { useUIExtension } from '../../../../hooks';
import type { PackagePolicyEditExtensionComponentProps } from '../../../../types';
-import {
- pkgKeyFromPackageInfo,
- isVerificationError,
- ExperimentalFeaturesService,
-} from '../../../../services';
+import { pkgKeyFromPackageInfo } from '../../../../services';
-import type {
- PackagePolicyFormState,
- AddToPolicyParams,
- CreatePackagePolicyParams,
-} from '../types';
+import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types';
import { IntegrationBreadcrumb } from '../components';
-import type { PackagePolicyValidationResults } from '../services';
-import { validatePackagePolicy, validationHasErrors } from '../services';
import {
StepConfigurePackagePolicy,
StepDefinePackagePolicy,
SelectedPolicyTab,
StepSelectHosts,
} from '../components';
-import {
- generateCreatePackagePolicyDevToolsRequest,
- generateCreateAgentPolicyDevToolsRequest,
-} from '../../services';
import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from './components';
+import { useDevToolsRequest, useOnSubmit } from './hooks';
const StepsWithLessPadding = styled(EuiSteps)`
.euiStep__content {
@@ -106,12 +74,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
from,
queryParamsPolicyId,
}) => {
- const { notifications } = useStartServices();
const {
agents: { enabled: isFleetEnabled },
} = useConfig();
const { params } = useRouteMatch();
- const [agentPolicy, setAgentPolicy] = useState();
const [newAgentPolicy, setNewAgentPolicy] = useState({
name: 'Agent policy 1',
@@ -123,64 +89,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
const [withSysMonitoring, setWithSysMonitoring] = useState(true);
const validation = agentPolicyFormValidation(newAgentPolicy);
- // only used to store the resulting package policy once saved
- const [savedPackagePolicy, setSavedPackagePolicy] = useState();
-
- // Retrieve agent count
- const agentPolicyId = agentPolicy?.id;
-
- const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({
- from,
- pkgkey: params.pkgkey,
- agentPolicyId,
- });
- useEffect(() => {
- const getAgentCount = async () => {
- const { data } = await sendGetAgentStatus({ policyId: agentPolicyId });
- if (data?.results.total !== undefined) {
- setAgentCount(data.results.total);
- }
- };
-
- if (isFleetEnabled && agentPolicyId) {
- getAgentCount();
- }
- }, [agentPolicyId, isFleetEnabled]);
- const [agentCount, setAgentCount] = useState(0);
-
const [selectedPolicyTab, setSelectedPolicyTab] = useState(
queryParamsPolicyId ? SelectedPolicyTab.EXISTING : SelectedPolicyTab.NEW
);
- // New package policy state
- const [packagePolicy, setPackagePolicy] = useState({
- name: '',
- description: '',
- namespace: 'default',
- policy_id: '',
- enabled: true,
- inputs: [],
- });
-
- const onSaveNavigate = useOnSaveNavigate({
- packagePolicy,
- queryParamsPolicyId,
- });
- const navigateAddAgent = (policy?: PackagePolicy) =>
- onSaveNavigate(policy, ['openEnrollmentFlyout']);
-
- const navigateAddAgentHelp = (policy?: PackagePolicy) =>
- onSaveNavigate(policy, ['showAddAgentHelp']);
-
- const confirmForceInstall = useConfirmForceInstall();
-
- // Validation state
- const [validationResults, setValidationResults] = useState();
- const [hasAgentPolicyError, setHasAgentPolicyError] = useState(false);
-
- // Form state
- const [formState, setFormState] = useState('VALID');
-
const { pkgName, pkgVersion } = splitPkgKey(params.pkgkey);
// Fetch package info
const {
@@ -194,43 +106,50 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
}
}, [packageInfoData]);
- // Update agent policy method
- const updateAgentPolicy = useCallback(
- (updatedAgentPolicy: AgentPolicy | undefined) => {
- if (updatedAgentPolicy) {
- setAgentPolicy(updatedAgentPolicy);
- if (packageInfo) {
+ const [agentCount, setAgentCount] = useState(0);
+
+ // Save package policy
+ const {
+ onSubmit,
+ updatePackagePolicy,
+ packagePolicy,
+ agentPolicy,
+ updateAgentPolicy,
+ savedPackagePolicy,
+ formState,
+ setFormState,
+ navigateAddAgent,
+ navigateAddAgentHelp,
+ setHasAgentPolicyError,
+ validationResults,
+ hasAgentPolicyError,
+ } = useOnSubmit({
+ agentCount,
+ packageInfo,
+ newAgentPolicy,
+ selectedPolicyTab,
+ withSysMonitoring,
+ queryParamsPolicyId,
+ });
+
+ const setPolicyValidation = useCallback(
+ (selectedTab: SelectedPolicyTab, updatedAgentPolicy: NewAgentPolicy) => {
+ if (selectedTab === SelectedPolicyTab.NEW) {
+ if (
+ !updatedAgentPolicy.name ||
+ updatedAgentPolicy.name.trim() === '' ||
+ !updatedAgentPolicy.namespace ||
+ updatedAgentPolicy.namespace.trim() === ''
+ ) {
+ setHasAgentPolicyError(true);
+ } else {
setHasAgentPolicyError(false);
}
- } else {
- setHasAgentPolicyError(true);
- setAgentPolicy(undefined);
}
-
- // eslint-disable-next-line no-console
- console.debug('Agent policy updated', updatedAgentPolicy);
},
- [packageInfo, setAgentPolicy]
+ [setHasAgentPolicyError]
);
- const setPolicyValidation = (
- selectedTab: SelectedPolicyTab,
- updatedAgentPolicy: NewAgentPolicy
- ) => {
- if (selectedTab === SelectedPolicyTab.NEW) {
- if (
- !updatedAgentPolicy.name ||
- updatedAgentPolicy.name.trim() === '' ||
- !updatedAgentPolicy.namespace ||
- updatedAgentPolicy.namespace.trim() === ''
- ) {
- setHasAgentPolicyError(true);
- } else {
- setHasAgentPolicyError(false);
- }
- }
- };
-
const updateNewAgentPolicy = useCallback(
(updatedFields: Partial) => {
const updatedAgentPolicy = {
@@ -240,7 +159,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
setNewAgentPolicy(updatedAgentPolicy);
setPolicyValidation(selectedPolicyTab, updatedAgentPolicy);
},
- [setNewAgentPolicy, newAgentPolicy, selectedPolicyTab]
+ [setNewAgentPolicy, setPolicyValidation, newAgentPolicy, selectedPolicyTab]
);
const updateSelectedPolicyTab = useCallback(
@@ -248,58 +167,29 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
setSelectedPolicyTab(selectedTab);
setPolicyValidation(selectedTab, newAgentPolicy);
},
- [setSelectedPolicyTab, newAgentPolicy]
+ [setSelectedPolicyTab, setPolicyValidation, newAgentPolicy]
);
- const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
- // Update package policy validation
- const updatePackagePolicyValidation = useCallback(
- (newPackagePolicy?: NewPackagePolicy) => {
- if (packageInfo) {
- const newValidationResult = validatePackagePolicy(
- newPackagePolicy || packagePolicy,
- packageInfo,
- safeLoad
- );
- setValidationResults(newValidationResult);
- // eslint-disable-next-line no-console
- console.debug('Package policy validation results', newValidationResult);
-
- return newValidationResult;
- }
- },
- [packagePolicy, packageInfo]
- );
+ // Retrieve agent count
+ const agentPolicyId = agentPolicy?.id;
- // Update package policy method
- const updatePackagePolicy = useCallback(
- (updatedFields: Partial) => {
- const newPackagePolicy = {
- ...packagePolicy,
- ...updatedFields,
- };
- setPackagePolicy(newPackagePolicy);
-
- // eslint-disable-next-line no-console
- console.debug('Package policy updated', newPackagePolicy);
- const newValidationResults = updatePackagePolicyValidation(newPackagePolicy);
- const hasPackage = newPackagePolicy.package;
- const hasValidationErrors = newValidationResults
- ? validationHasErrors(newValidationResults)
- : false;
- const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== '';
- if (
- hasPackage &&
- (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) &&
- !hasValidationErrors
- ) {
- setFormState('VALID');
- } else {
- setFormState('INVALID');
+ const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({
+ from,
+ pkgkey: params.pkgkey,
+ agentPolicyId,
+ });
+ useEffect(() => {
+ const getAgentCount = async () => {
+ const { data } = await sendGetAgentStatus({ policyId: agentPolicyId });
+ if (data?.results.total !== undefined) {
+ setAgentCount(data.results.total);
}
- },
- [packagePolicy, updatePackagePolicyValidation, selectedPolicyTab]
- );
+ };
+
+ if (isFleetEnabled && agentPolicyId) {
+ getAgentCount();
+ }
+ }, [agentPolicyId, isFleetEnabled]);
const handleExtensionViewOnChange = useCallback<
PackagePolicyEditExtensionComponentProps['onChange']
@@ -313,132 +203,16 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
return prevState;
});
},
- [updatePackagePolicy]
- );
-
- // Save package policy
- const savePackagePolicy = useCallback(
- async (pkgPolicy: CreatePackagePolicyRequest['body']) => {
- setFormState('LOADING');
- const result = await sendCreatePackagePolicy(pkgPolicy);
- setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS');
- return result;
- },
- [agentCount]
+ [updatePackagePolicy, setFormState]
);
- const createAgentPolicy = useCallback(async (): Promise => {
- let createdAgentPolicy;
- setFormState('LOADING');
- // do not create agent policy with system integration if package policy already is for system package
- const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
- const resp = await sendCreateAgentPolicy(newAgentPolicy, {
- withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem,
- });
- if (resp.error) {
- setFormState('VALID');
- throw resp.error;
- }
- if (resp.data) {
- createdAgentPolicy = resp.data.item;
- setAgentPolicy(createdAgentPolicy);
- updatePackagePolicy({ policy_id: createdAgentPolicy.id });
- }
- return createdAgentPolicy;
- }, [packagePolicy?.package?.name, newAgentPolicy, withSysMonitoring, updatePackagePolicy]);
-
- const onSubmit = useCallback(
- async ({
- force,
- overrideCreatedAgentPolicy,
- }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => {
- if (formState === 'VALID' && hasErrors) {
- setFormState('INVALID');
- return;
- }
- if (agentCount !== 0 && formState !== 'CONFIRM') {
- setFormState('CONFIRM');
- return;
- }
- let createdPolicy = overrideCreatedAgentPolicy;
- if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) {
- try {
- createdPolicy = await createAgentPolicy();
- } catch (e) {
- notifications.toasts.addError(e, {
- title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', {
- defaultMessage: 'Unable to create agent policy',
- }),
- });
- return;
- }
- }
-
- setFormState('LOADING');
- // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately
- const { error, data } = await savePackagePolicy({
- ...packagePolicy,
- policy_id: createdPolicy?.id ?? packagePolicy.policy_id,
- force,
- });
- if (!error) {
- setSavedPackagePolicy(data!.item);
-
- const hasAgentsAssigned = agentCount && agentPolicy;
- if (!hasAgentsAssigned) {
- setFormState('SUBMITTED_NO_AGENTS');
- return;
- }
- onSaveNavigate(data!.item);
-
- notifications.toasts.addSuccess({
- title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', {
- defaultMessage: `'{packagePolicyName}' integration added.`,
- values: {
- packagePolicyName: packagePolicy.name,
- },
- }),
- text: hasAgentsAssigned
- ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
- defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
- values: {
- agentPolicyName: agentPolicy!.name,
- },
- })
- : undefined,
- 'data-test-subj': 'packagePolicyCreateSuccessToast',
- });
- } else {
- if (isVerificationError(error)) {
- setFormState('VALID'); // don't show the add agent modal
- const forceInstall = await confirmForceInstall(packagePolicy.package!);
-
- if (forceInstall) {
- // skip creating the agent policy because it will have already been successfully created
- onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true });
- }
- return;
- }
- notifications.toasts.addError(error, {
- title: 'Error',
- });
- setFormState('VALID');
- }
- },
- [
- formState,
- hasErrors,
- agentCount,
- selectedPolicyTab,
- savePackagePolicy,
- packagePolicy,
- createAgentPolicy,
- notifications.toasts,
- agentPolicy,
- onSaveNavigate,
- confirmForceInstall,
- ]
- );
+ const { devtoolRequest, devtoolRequestDescription, showDevtoolsRequest } = useDevToolsRequest({
+ newAgentPolicy,
+ packagePolicy,
+ selectedPolicyTab,
+ withSysMonitoring,
+ packageInfo,
+ });
const integrationInfo = useMemo(
() =>
@@ -488,6 +262,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
withSysMonitoring,
updateSelectedPolicyTab,
queryParamsPolicyId,
+ setHasAgentPolicyError,
]
);
@@ -564,47 +339,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
},
];
- const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } =
- ExperimentalFeaturesService.get();
-
- const showDevtoolsRequest =
- !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') &&
- isShowDevtoolRequestExperimentEnabled;
-
- const [devtoolRequest, devtoolRequestDescription] = useMemo(() => {
- if (selectedPolicyTab === SelectedPolicyTab.NEW) {
- const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
- return [
- `${generateCreateAgentPolicyDevToolsRequest(
- newAgentPolicy,
- withSysMonitoring && !packagePolicyIsSystem
- )}\n\n${generateCreatePackagePolicyDevToolsRequest({
- ...packagePolicy,
- })}`,
- i18n.translate(
- 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription',
- {
- defaultMessage:
- 'These Kibana requests creates a new agent policy and a new package policy.',
- }
- ),
- ];
- }
-
- return [
- generateCreatePackagePolicyDevToolsRequest({
- ...packagePolicy,
- }),
- i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', {
- defaultMessage: 'This Kibana request creates a new package policy.',
- }),
- ];
- }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]);
-
// Display package error if there is one
if (packageInfoError) {
return (
- {
+ return sendRequest({
+ path: epmRouteService.getBulkInstallPath(),
+ method: 'post',
+ body: {
+ packages,
+ },
+ });
+};
+
export const sendRemovePackage = (pkgName: string, pkgVersion: string, force: boolean = false) => {
return sendRequest({
path: epmRouteService.getRemovePath(pkgName, pkgVersion),
From 0e7070ad71a75b317314654510e3e5d5317e9db4 Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Tue, 4 Oct 2022 11:32:54 -0400
Subject: [PATCH 29/39] [ResponseOps][Stack Connectors] Adding custom
validators to sub actions framework (#142376)
* Adding custom validators
* Updating README and tests
* Fixing translation error
* Addressing feedback
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../actions/server/actions_config.test.ts | 9 +++
x-pack/plugins/actions/server/index.ts | 2 +
.../server/sub_action_framework/README.md | 18 +++++-
.../sub_action_framework/helpers/index.ts | 8 +++
.../helpers/validators.ts | 30 ++++++++++
.../server/sub_action_framework/types.ts | 26 +++++++-
.../sub_action_framework/validators.test.ts | 59 ++++++++++++++++++-
.../server/sub_action_framework/validators.ts | 40 ++++++++++++-
x-pack/plugins/actions/server/types.ts | 2 +-
9 files changed, 188 insertions(+), 6 deletions(-)
create mode 100644 x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts
create mode 100644 x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts
diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts
index a6b68d907cb44..b1af4a843b496 100644
--- a/x-pack/plugins/actions/server/actions_config.test.ts
+++ b/x-pack/plugins/actions/server/actions_config.test.ts
@@ -43,6 +43,15 @@ const defaultActionsConfig: ActionsConfig = {
};
describe('ensureUriAllowed', () => {
+ test('throws an error when the Uri is an empty string', () => {
+ const config: ActionsConfig = defaultActionsConfig;
+ expect(() =>
+ getActionsConfigurationUtilities(config).ensureUriAllowed('')
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"target url \\"\\" is not added to the Kibana config xpack.actions.allowedHosts"`
+ );
+ });
+
test('returns true when "any" hostnames are allowed', () => {
const config: ActionsConfig = {
...defaultActionsConfig,
diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts
index 7f9b45c368e90..1c7a66978ffb3 100644
--- a/x-pack/plugins/actions/server/index.ts
+++ b/x-pack/plugins/actions/server/index.ts
@@ -139,3 +139,5 @@ export const config: PluginConfigDescriptor = {
},
],
};
+
+export { urlAllowListValidator } from './sub_action_framework/helpers';
diff --git a/x-pack/plugins/actions/server/sub_action_framework/README.md b/x-pack/plugins/actions/server/sub_action_framework/README.md
index 90951692f5457..7c2ab0755a0ad 100644
--- a/x-pack/plugins/actions/server/sub_action_framework/README.md
+++ b/x-pack/plugins/actions/server/sub_action_framework/README.md
@@ -6,6 +6,7 @@ The Kibana actions plugin provides a framework to create executable actions that
- Register a sub action and map it to a function of your choice.
- Define a schema for the parameters of your sub action.
+- Define custom validators (or use the provided helpers) for the parameters of your sub action.
- Define a response schema for responses from external services.
- Create connectors that are supported by the Cases management system.
@@ -353,4 +354,19 @@ plugins.actions.registerSubActionConnectorType({
});
```
-You can see a full example in [x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts](../../../../test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts)
\ No newline at end of file
+You can see a full example in [x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts](../../../../test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts)
+
+### Example: Register sub action connector with custom validators
+
+The sub actions framework allows custom validators during registration of the connector type. Below is an example of including the URL validation for the `TestSubActionConnector` `url` configuration field.
+
+```typescript
+plugins.actions.registerSubActionConnectorType({
+ id: '.test-sub-action-connector',
+ name: 'Test: Sub action connector',
+ minimumLicenseRequired: 'platinum' as const,
+ schema: { config: TestConfigSchema, secrets: TestSecretsSchema },
+ validators: [{type: ValidatorType.CONFIG, validate: urlAllowListValidator('url')}]
+ Service: TestSubActionConnector,
+});
+```
diff --git a/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts b/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts
new file mode 100644
index 0000000000000..c69caff6b0c71
--- /dev/null
+++ b/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { urlAllowListValidator } from './validators';
diff --git a/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts b/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts
new file mode 100644
index 0000000000000..7618fef0f3ea4
--- /dev/null
+++ b/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { get } from 'lodash';
+import { ValidatorServices } from '../../types';
+
+export const urlAllowListValidator = (urlKey: string) => {
+ return (obj: T, validatorServices: ValidatorServices) => {
+ const { configurationUtilities } = validatorServices;
+ try {
+ const url = get(obj, urlKey, '');
+
+ configurationUtilities.ensureUriAllowed(url);
+ } catch (allowListError) {
+ throw new Error(
+ i18n.translate('xpack.actions.subActionsFramework.urlValidationError', {
+ defaultMessage: 'error validating url: {message}',
+ values: {
+ message: allowListError.message,
+ },
+ })
+ );
+ }
+ };
+};
diff --git a/x-pack/plugins/actions/server/sub_action_framework/types.ts b/x-pack/plugins/actions/server/sub_action_framework/types.ts
index cdc05524cf842..f584d73d24443 100644
--- a/x-pack/plugins/actions/server/sub_action_framework/types.ts
+++ b/x-pack/plugins/actions/server/sub_action_framework/types.ts
@@ -10,7 +10,7 @@ import { Logger } from '@kbn/logging';
import type { LicenseType } from '@kbn/licensing-plugin/common/types';
import { ActionsConfigurationUtilities } from '../actions_config';
-import { ActionTypeParams, Services } from '../types';
+import { ActionTypeParams, Services, ValidatorType as ValidationSchema } from '../types';
import { SubActionConnector } from './sub_action_connector';
export interface ServiceParams {
@@ -34,6 +34,29 @@ export type IServiceAbstract = abstract new (
params: ServiceParams
) => SubActionConnector;
+export enum ValidatorType {
+ CONFIG,
+ SECRETS,
+}
+
+interface Validate {
+ validator: ValidateFn;
+}
+
+export type ValidateFn = NonNullable['customValidator']>;
+
+interface ConfigValidator extends Validate {
+ type: ValidatorType.CONFIG;
+}
+
+interface SecretsValidator extends Validate {
+ type: ValidatorType.SECRETS;
+}
+
+export type Validators = Array<
+ ConfigValidator | SecretsValidator
+>;
+
export interface SubActionConnectorType {
id: string;
name: string;
@@ -43,6 +66,7 @@ export interface SubActionConnectorType {
config: Type;
secrets: Type;
};
+ validators?: Array | SecretsValidator>;
Service: IService;
}
diff --git a/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts b/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts
index 6cae35141b498..b28adc0b545bf 100644
--- a/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts
+++ b/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts
@@ -14,7 +14,7 @@ import {
TestSecrets,
TestSubActionConnector,
} from './mocks';
-import { IService } from './types';
+import { IService, SubActionConnectorType, ValidatorType } from './types';
import { buildValidators } from './validators';
describe('Validators', () => {
@@ -36,6 +36,39 @@ describe('Validators', () => {
return buildValidators({ configurationUtilities: mockedActionsConfig, connector });
};
+ const createValidatorWithCustomValidation = (Service: IService) => {
+ const configValidator = jest.fn();
+ const secretsValidator = jest.fn();
+
+ const connector: SubActionConnectorType = {
+ id: '.test',
+ name: 'Test',
+ minimumLicenseRequired: 'basic' as const,
+ supportedFeatureIds: ['alerting'],
+ schema: {
+ config: TestConfigSchema,
+ secrets: TestSecretsSchema,
+ },
+ validators: [
+ {
+ type: ValidatorType.CONFIG,
+ validator: configValidator,
+ },
+ {
+ type: ValidatorType.SECRETS,
+ validator: secretsValidator,
+ },
+ ],
+ Service,
+ };
+
+ return {
+ validators: buildValidators({ configurationUtilities: mockedActionsConfig, connector }),
+ configValidator,
+ secretsValidator,
+ };
+ };
+
beforeEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
@@ -96,4 +129,28 @@ describe('Validators', () => {
const { params } = validator;
expect(() => params.schema.validate({ subAction, subActionParams: {} })).toThrow();
});
+
+ it('calls the config and secrets custom validator functions', () => {
+ const validator = createValidatorWithCustomValidation(TestSubActionConnector);
+
+ validator.validators.config.customValidator?.(
+ { url: 'http://www.example.com' },
+ { configurationUtilities: mockedActionsConfig }
+ );
+
+ validator.validators.secrets.customValidator?.(
+ { password: '123', username: 'sam' },
+ { configurationUtilities: mockedActionsConfig }
+ );
+
+ expect(validator.configValidator).toHaveBeenCalledWith(
+ { url: 'http://www.example.com' },
+ expect.anything()
+ );
+
+ expect(validator.secretsValidator).toHaveBeenCalledWith(
+ { password: '123', username: 'sam' },
+ expect.anything()
+ );
+ });
});
diff --git a/x-pack/plugins/actions/server/sub_action_framework/validators.ts b/x-pack/plugins/actions/server/sub_action_framework/validators.ts
index be6dafed28163..e9cbbb3ae8f80 100644
--- a/x-pack/plugins/actions/server/sub_action_framework/validators.ts
+++ b/x-pack/plugins/actions/server/sub_action_framework/validators.ts
@@ -7,8 +7,8 @@
import { schema } from '@kbn/config-schema';
import { ActionsConfigurationUtilities } from '../actions_config';
-import { ActionTypeConfig, ActionTypeSecrets } from '../types';
-import { SubActionConnectorType } from './types';
+import { ActionTypeConfig, ActionTypeSecrets, ValidatorServices } from '../types';
+import { SubActionConnectorType, ValidateFn, Validators, ValidatorType } from './types';
export const buildValidators = <
Config extends ActionTypeConfig,
@@ -20,12 +20,16 @@ export const buildValidators = <
configurationUtilities: ActionsConfigurationUtilities;
connector: SubActionConnectorType;
}) => {
+ const { config, secrets } = buildCustomValidators(connector.validators);
+
return {
config: {
schema: connector.schema.config,
+ customValidator: config,
},
secrets: {
schema: connector.schema.secrets,
+ customValidator: secrets,
},
params: {
schema: schema.object({
@@ -42,3 +46,35 @@ export const buildValidators = <
},
};
};
+
+const buildCustomValidators = (validators?: Validators) => {
+ const partitionedValidators: {
+ config: Array>;
+ secrets: Array>;
+ } = { config: [], secrets: [] };
+
+ for (const validatorInfo of validators ?? []) {
+ if (validatorInfo.type === ValidatorType.CONFIG) {
+ partitionedValidators.config.push(validatorInfo.validator);
+ } else {
+ partitionedValidators.secrets.push(validatorInfo.validator);
+ }
+ }
+
+ return {
+ config: createCustomValidatorFunction(partitionedValidators.config),
+ secrets: createCustomValidatorFunction(partitionedValidators.secrets),
+ };
+};
+
+const createCustomValidatorFunction = (validators: Array>) => {
+ if (validators.length <= 0) {
+ return;
+ }
+
+ return (value: T, validatorServices: ValidatorServices) => {
+ for (const validate of validators) {
+ validate(value, validatorServices);
+ }
+ };
+};
diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts
index ae344d4f62dbc..c92761ad0a288 100644
--- a/x-pack/plugins/actions/server/types.ts
+++ b/x-pack/plugins/actions/server/types.ts
@@ -91,7 +91,7 @@ export type ExecutorType = (
options: ActionTypeExecutorOptions
) => Promise>;
-interface ValidatorType {
+export interface ValidatorType {
schema: {
validate(value: unknown): Type;
};
From 25b79a9cdbc1bf6809698d4cfad825850d6b7923 Mon Sep 17 00:00:00 2001
From: Gerard Soldevila
Date: Tue, 4 Oct 2022 17:43:41 +0200
Subject: [PATCH 30/39] Collect metrics about the active/idle connections to ES
nodes (#141434)
* Collect metrics about the connections from esClient to ES nodes
* Misc enhancements following PR remarks and comments
* Fix UTs
* Fix mock typings
* Minimize API surface, fix mocks typings
* Fix incomplete mocks
* Fix renameed agentManager => agentStore in remaining UT
* Cover edge cases for getAgentsSocketsStats()
* Misc NIT enhancements
* Revert incorrect import type statements
---
.../src/status/lib/load_status.test.ts | 13 ++
.../index.ts | 2 +-
.../src/agent_manager.test.ts | 21 ++-
.../src/agent_manager.ts | 36 +++--
.../src/cluster_client.test.ts | 52 +++----
.../src/cluster_client.ts | 15 +-
.../src/configure_client.test.ts | 60 +++----
.../src/configure_client.ts | 8 +-
.../index.ts | 1 +
.../src/agent_manager.mocks.ts | 13 ++
.../src/elasticsearch_service.test.mocks.ts | 8 +-
.../src/elasticsearch_service.test.ts | 9 +-
.../src/elasticsearch_service.ts | 3 +-
.../src/types.ts | 2 +
.../src/elasticsearch_service.mock.ts | 2 +
.../src/client/cluster_client.ts | 4 +-
.../BUILD.bazel | 3 +
.../index.ts | 1 +
.../src/elasticsearch_client.test.ts | 33 ++++
.../src/elasticsearch_client.ts | 26 ++++
.../get_agents_sockets_stats.test.mocks.ts | 29 ++++
.../src/get_agents_sockets_stats.test.ts | 147 ++++++++++++++++++
.../src/get_agents_sockets_stats.ts | 81 ++++++++++
.../core-metrics-server-internal/BUILD.bazel | 9 +-
.../src/logging/get_ops_metrics_log.test.ts | 2 +
.../src/metrics_service.test.ts | 28 ++--
.../src/metrics_service.ts | 9 +-
.../src/ops_metrics_collector.test.mocks.ts | 2 +
.../src/ops_metrics_collector.test.ts | 9 +-
.../src/ops_metrics_collector.ts | 10 +-
.../core-metrics-server-mocks/index.ts | 2 +-
.../src/metrics_service.mock.ts | 21 ++-
.../core/metrics/core-metrics-server/index.ts | 2 +
.../core-metrics-server/src/metrics.ts | 42 +++++
.../src/routes/status.ts | 1 +
src/cli_setup/utils.ts | 2 +-
src/core/server/server.ts | 5 +-
.../ops_stats_collector.test.ts.snap | 13 ++
38 files changed, 617 insertions(+), 109 deletions(-)
create mode 100644 packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts
create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts
create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts
create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts
create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts
create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts
diff --git a/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts b/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts
index ac40eedfccb7d..dd750a56fbf2d 100644
--- a/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts
+++ b/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts
@@ -61,6 +61,19 @@ const mockedResponse: StatusResponse = {
'15m': 0.1,
},
},
+ elasticsearch_client: {
+ protocol: 'https',
+ connectedNodes: 3,
+ nodesWithActiveSockets: 3,
+ nodesWithIdleSockets: 1,
+ totalActiveSockets: 25,
+ totalIdleSockets: 2,
+ totalQueuedRequests: 0,
+ mostActiveNodeSockets: 15,
+ averageActiveSocketsPerNode: 8,
+ mostIdleNodeSockets: 2,
+ averageIdleSocketsPerNode: 0.5,
+ },
process: {
pid: 1,
memory: {
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts
index aa1364c179e18..6f1f276c7d089 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts
@@ -9,7 +9,7 @@
export { ScopedClusterClient } from './src/scoped_cluster_client';
export { ClusterClient } from './src/cluster_client';
export { configureClient } from './src/configure_client';
-export { AgentManager } from './src/agent_manager';
+export { type AgentStore, AgentManager } from './src/agent_manager';
export { getRequestDebugMeta, getErrorMessage } from './src/log_query_and_deprecation';
export {
PRODUCT_RESPONSE_HEADER,
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts
index 811d9d95831ef..dfa8a077d2e53 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts
@@ -104,10 +104,10 @@ describe('AgentManager', () => {
const agentFactory = agentManager.getAgentFactory();
const agent = agentFactory({ url: new URL('http://elastic-node-1:9200') });
// eslint-disable-next-line dot-notation
- expect(agentManager['httpStore'].has(agent)).toEqual(true);
+ expect(agentManager['agents'].has(agent)).toEqual(true);
agent.destroy();
// eslint-disable-next-line dot-notation
- expect(agentManager['httpStore'].has(agent)).toEqual(false);
+ expect(agentManager['agents'].has(agent)).toEqual(false);
});
});
@@ -122,4 +122,21 @@ describe('AgentManager', () => {
});
});
});
+
+ describe('#getAgents()', () => {
+ it('returns the created HTTP and HTTPs Agent instances', () => {
+ const agentManager = new AgentManager();
+ const agentFactory1 = agentManager.getAgentFactory();
+ const agentFactory2 = agentManager.getAgentFactory();
+ const agent1 = agentFactory1({ url: new URL('http://elastic-node-1:9200') });
+ const agent2 = agentFactory2({ url: new URL('http://elastic-node-1:9200') });
+ const agent3 = agentFactory1({ url: new URL('https://elastic-node-1:9200') });
+ const agent4 = agentFactory2({ url: new URL('https://elastic-node-1:9200') });
+
+ const agents = agentManager.getAgents();
+
+ expect(agents.size).toEqual(4);
+ expect([...agents]).toEqual(expect.arrayContaining([agent1, agent2, agent3, agent4]));
+ });
+ });
});
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts
index eb68014561d77..9a57cc44e04ad 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts
@@ -8,7 +8,7 @@
import { Agent as HttpAgent } from 'http';
import { Agent as HttpsAgent } from 'https';
-import { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch';
+import type { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch';
const HTTPS = 'https:';
const DEFAULT_CONFIG: HttpAgentOptions = {
@@ -22,6 +22,14 @@ const DEFAULT_CONFIG: HttpAgentOptions = {
export type NetworkAgent = HttpAgent | HttpsAgent;
export type AgentFactory = (connectionOpts: ConnectionOptions) => NetworkAgent;
+export interface AgentFactoryProvider {
+ getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory;
+}
+
+export interface AgentStore {
+ getAgents(): Set;
+}
+
/**
* Allows obtaining Agent factories, which can then be fed into elasticsearch-js's Client class.
* Ideally, we should obtain one Agent factory for each ES Client class.
@@ -33,15 +41,11 @@ export type AgentFactory = (connectionOpts: ConnectionOptions) => NetworkAgent;
* exposes methods that can modify the underlying pools, effectively impacting the connections of other Clients.
* @internal
**/
-export class AgentManager {
- // Stores Https Agent instances
- private httpsStore: Set;
- // Stores Http Agent instances
- private httpStore: Set;
+export class AgentManager implements AgentFactoryProvider, AgentStore {
+ private agents: Set;
constructor(private agentOptions: HttpAgentOptions = DEFAULT_CONFIG) {
- this.httpsStore = new Set();
- this.httpStore = new Set();
+ this.agents = new Set();
}
public getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory {
@@ -61,8 +65,8 @@ export class AgentManager {
connectionOpts.tls
);
httpsAgent = new HttpsAgent(config);
- this.httpsStore.add(httpsAgent);
- dereferenceOnDestroy(this.httpsStore, httpsAgent);
+ this.agents.add(httpsAgent);
+ dereferenceOnDestroy(this.agents, httpsAgent);
}
return httpsAgent;
@@ -71,19 +75,23 @@ export class AgentManager {
if (!httpAgent) {
const config = Object.assign({}, DEFAULT_CONFIG, this.agentOptions, agentOptions);
httpAgent = new HttpAgent(config);
- this.httpStore.add(httpAgent);
- dereferenceOnDestroy(this.httpStore, httpAgent);
+ this.agents.add(httpAgent);
+ dereferenceOnDestroy(this.agents, httpAgent);
}
return httpAgent;
};
}
+
+ public getAgents(): Set {
+ return this.agents;
+ }
}
-const dereferenceOnDestroy = (protocolStore: Set, agent: NetworkAgent) => {
+const dereferenceOnDestroy = (store: Set, agent: NetworkAgent) => {
const doDestroy = agent.destroy.bind(agent);
agent.destroy = () => {
- protocolStore.delete(agent);
+ store.delete(agent);
doDestroy();
};
};
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts
index e5be9fc0ab718..f371e3425b0c7 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts
@@ -46,7 +46,7 @@ describe('ClusterClient', () => {
let authHeaders: ReturnType;
let internalClient: jest.Mocked;
let scopedClient: jest.Mocked;
- let agentManager: AgentManager;
+ let agentFactoryProvider: AgentManager;
const mockTransport = { mockTransport: true };
@@ -54,7 +54,7 @@ describe('ClusterClient', () => {
logger = loggingSystemMock.createLogger();
internalClient = createClient();
scopedClient = createClient();
- agentManager = new AgentManager();
+ agentFactoryProvider = new AgentManager();
authHeaders = httpServiceMock.createAuthHeaderStorage();
authHeaders.get.mockImplementation(() => ({
@@ -84,21 +84,21 @@ describe('ClusterClient', () => {
authHeaders,
type: 'custom-type',
getExecutionContext: getExecutionContextMock,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
expect(configureClientMock).toHaveBeenCalledTimes(2);
expect(configureClientMock).toHaveBeenCalledWith(config, {
logger,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
type: 'custom-type',
getExecutionContext: getExecutionContextMock,
});
expect(configureClientMock).toHaveBeenCalledWith(config, {
logger,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
type: 'custom-type',
getExecutionContext: getExecutionContextMock,
@@ -113,7 +113,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -128,7 +128,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@@ -155,7 +155,7 @@ describe('ClusterClient', () => {
authHeaders,
getExecutionContext,
getUnauthorizedErrorHandler,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@@ -179,7 +179,7 @@ describe('ClusterClient', () => {
authHeaders,
getExecutionContext,
getUnauthorizedErrorHandler,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@@ -212,7 +212,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@@ -237,7 +237,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
@@ -271,7 +271,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({});
@@ -305,7 +305,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
@@ -344,7 +344,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({});
@@ -373,7 +373,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
@@ -410,7 +410,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({});
@@ -445,7 +445,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
@@ -482,7 +482,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@@ -513,7 +513,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
@@ -547,7 +547,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
@@ -579,7 +579,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = {
@@ -612,7 +612,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
const request = {
@@ -640,7 +640,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -658,7 +658,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -703,7 +703,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -720,7 +720,7 @@ describe('ClusterClient', () => {
logger,
type: 'custom-type',
authHeaders,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts
index f243c98ecf798..2a2f6ef1334a2 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts
@@ -24,9 +24,12 @@ import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server';
import { configureClient } from './configure_client';
import { ScopedClusterClient } from './scoped_cluster_client';
import { getDefaultHeaders } from './headers';
-import { createInternalErrorHandler, InternalUnauthorizedErrorHandler } from './retry_unauthorized';
+import {
+ createInternalErrorHandler,
+ type InternalUnauthorizedErrorHandler,
+} from './retry_unauthorized';
import { createTransport } from './create_transport';
-import { AgentManager } from './agent_manager';
+import type { AgentFactoryProvider } from './agent_manager';
const noop = () => undefined;
@@ -49,7 +52,7 @@ export class ClusterClient implements ICustomClusterClient {
authHeaders,
getExecutionContext = noop,
getUnauthorizedErrorHandler = noop,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
}: {
config: ElasticsearchClientConfig;
@@ -58,7 +61,7 @@ export class ClusterClient implements ICustomClusterClient {
authHeaders?: IAuthHeadersStorage;
getExecutionContext?: () => string | undefined;
getUnauthorizedErrorHandler?: () => UnauthorizedErrorHandler | undefined;
- agentManager: AgentManager;
+ agentFactoryProvider: AgentFactoryProvider;
kibanaVersion: string;
}) {
this.config = config;
@@ -71,7 +74,7 @@ export class ClusterClient implements ICustomClusterClient {
logger,
type,
getExecutionContext,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
this.rootScopedClient = configureClient(config, {
@@ -79,7 +82,7 @@ export class ClusterClient implements ICustomClusterClient {
type,
getExecutionContext,
scoped: true,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
}
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts
index 40824d306ac48..fe511f46278d9 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts
@@ -10,7 +10,6 @@ jest.mock('./log_query_and_deprecation', () => ({
__esModule: true,
instrumentEsQueryAndDeprecationLogger: jest.fn(),
}));
-jest.mock('./agent_manager');
import { Agent } from 'http';
import {
@@ -24,9 +23,8 @@ import { ClusterConnectionPool } from '@elastic/elasticsearch';
import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server';
import { configureClient } from './configure_client';
import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation';
-import { AgentManager } from './agent_manager';
+import { type AgentFactoryProvider, AgentManager } from './agent_manager';
-const AgentManagerMock = AgentManager as jest.Mock;
const kibanaVersion = '1.0.0';
const createFakeConfig = (
@@ -46,31 +44,17 @@ const createFakeClient = () => {
return client;
};
-const createFakeAgentFactory = (logger: MockedLogger) => {
- const agentFactory = () => new Agent();
-
- AgentManagerMock.mockImplementationOnce(() => {
- const agentManager = new AgentManager();
- agentManager.getAgentFactory = () => agentFactory;
- return agentManager;
- });
-
- const agentManager = new AgentManager();
-
- return { agentManager, agentFactory };
-};
-
describe('configureClient', () => {
let logger: MockedLogger;
let config: ElasticsearchClientConfig;
- let agentManager: AgentManager;
+ let agentFactoryProvider: AgentFactoryProvider;
beforeEach(() => {
logger = loggingSystemMock.createLogger();
config = createFakeConfig();
parseClientOptionsMock.mockReturnValue({});
ClientMock.mockImplementation(() => createFakeClient());
- agentManager = new AgentManager();
+ agentFactoryProvider = new AgentManager();
});
afterEach(() => {
@@ -80,14 +64,26 @@ describe('configureClient', () => {
});
it('calls `parseClientOptions` with the correct parameters', () => {
- configureClient(config, { logger, type: 'test', scoped: false, agentManager, kibanaVersion });
+ configureClient(config, {
+ logger,
+ type: 'test',
+ scoped: false,
+ agentFactoryProvider,
+ kibanaVersion,
+ });
expect(parseClientOptionsMock).toHaveBeenCalledTimes(1);
expect(parseClientOptionsMock).toHaveBeenCalledWith(config, false, kibanaVersion);
parseClientOptionsMock.mockClear();
- configureClient(config, { logger, type: 'test', scoped: true, agentManager, kibanaVersion });
+ configureClient(config, {
+ logger,
+ type: 'test',
+ scoped: true,
+ agentFactoryProvider,
+ kibanaVersion,
+ });
expect(parseClientOptionsMock).toHaveBeenCalledTimes(1);
expect(parseClientOptionsMock).toHaveBeenCalledWith(config, true, kibanaVersion);
@@ -103,7 +99,7 @@ describe('configureClient', () => {
logger,
type: 'test',
scoped: false,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -112,13 +108,17 @@ describe('configureClient', () => {
expect(client).toBe(ClientMock.mock.results[0].value);
});
- it('constructs a client using the provided `agentManager`', () => {
- const { agentManager: customAgentManager, agentFactory } = createFakeAgentFactory(logger);
+ it('constructs a client using the provided `agentFactoryProvider`', () => {
+ const agentFactory = () => new Agent();
+ const customAgentFactoryProvider = {
+ getAgentFactory: () => agentFactory,
+ };
+
const client = configureClient(config, {
logger,
type: 'test',
scoped: false,
- agentManager: customAgentManager,
+ agentFactoryProvider: customAgentFactoryProvider,
kibanaVersion,
});
@@ -134,7 +134,7 @@ describe('configureClient', () => {
type: 'test',
scoped: false,
getExecutionContext,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -148,7 +148,7 @@ describe('configureClient', () => {
type: 'test',
scoped: true,
getExecutionContext,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -164,7 +164,7 @@ describe('configureClient', () => {
logger,
type: 'test',
scoped: false,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -185,7 +185,7 @@ describe('configureClient', () => {
logger,
type: 'test',
scoped: false,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
@@ -203,7 +203,7 @@ describe('configureClient', () => {
logger,
type: 'test',
scoped: false,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
});
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts
index e1c8048c6a89e..2fd7a4d4a74bb 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts
@@ -12,7 +12,7 @@ import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server';
import { parseClientOptions } from './client_config';
import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation';
import { createTransport } from './create_transport';
-import { AgentManager } from './agent_manager';
+import type { AgentFactoryProvider } from './agent_manager';
const noop = () => undefined;
@@ -23,14 +23,14 @@ export const configureClient = (
type,
scoped = false,
getExecutionContext = noop,
- agentManager,
+ agentFactoryProvider,
kibanaVersion,
}: {
logger: Logger;
type: string;
scoped?: boolean;
getExecutionContext?: () => string | undefined;
- agentManager: AgentManager;
+ agentFactoryProvider: AgentFactoryProvider;
kibanaVersion: string;
}
): Client => {
@@ -38,7 +38,7 @@ export const configureClient = (
const KibanaTransport = createTransport({ getExecutionContext });
const client = new Client({
...clientOptions,
- agent: agentManager.getAgentFactory(clientOptions.agent),
+ agent: agentFactoryProvider.getAgentFactory(clientOptions.agent),
Transport: KibanaTransport,
Connection: HttpConnection,
// using ClusterConnectionPool until https://github.com/elastic/elasticsearch-js/issues/1714 is addressed
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts
index c46381d57a7b6..0b66d449df013 100644
--- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts
@@ -15,3 +15,4 @@ export type {
DeeplyMockedApi,
ElasticsearchClientMock,
} from './src/mocks';
+export { createAgentStoreMock } from './src/agent_manager.mocks';
diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts
new file mode 100644
index 0000000000000..2fd8812b3aae0
--- /dev/null
+++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { AgentStore, NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal';
+
+export const createAgentStoreMock = (agents: Set = new Set()): AgentStore => ({
+ getAgents: jest.fn(() => agents),
+});
diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts
index cd6d36f0cb111..68a56ff28bc8d 100644
--- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts
@@ -6,8 +6,14 @@
* Side Public License, v 1.
*/
+import type { AgentManager } from '@kbn/core-elasticsearch-client-server-internal';
+
export const MockClusterClient = jest.fn();
-export const MockAgentManager = jest.fn();
+export const MockAgentManager: jest.MockedClass = jest.fn().mockReturnValue({
+ getAgents: jest.fn(),
+ getAgentFactory: jest.fn(),
+});
+
jest.mock('@kbn/core-elasticsearch-client-server-internal', () => ({
ClusterClient: MockClusterClient,
AgentManager: MockAgentManager,
diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts
index 5b54a2c35683e..ecd364b4283cf 100644
--- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts
@@ -135,7 +135,7 @@ describe('#preboot', () => {
);
});
- it('creates a ClusterClient using the internal AgentManager', async () => {
+ it('creates a ClusterClient using the internal AgentManager as AgentFactoryProvider ', async () => {
const prebootContract = await elasticsearchService.preboot();
const customConfig = { keepAlive: true };
const clusterClient = prebootContract.createClient('custom-type', customConfig);
@@ -145,7 +145,7 @@ describe('#preboot', () => {
expect(MockClusterClient).toHaveBeenCalledTimes(1);
expect(MockClusterClient.mock.calls[0][0]).toEqual(
// eslint-disable-next-line dot-notation
- expect.objectContaining({ agentManager: elasticsearchService['agentManager'] })
+ expect.objectContaining({ agentFactoryProvider: elasticsearchService['agentManager'] })
);
});
@@ -201,6 +201,11 @@ describe('#setup', () => {
);
});
+ it('returns an AgentStore as part of the contract', async () => {
+ const setupContract = await elasticsearchService.setup(setupDeps);
+ expect(typeof setupContract.agentStore.getAgents).toEqual('function');
+ });
+
it('esNodeVersionCompatibility$ only starts polling when subscribed to', async () => {
const mockedClient = mockClusterClientInstance.asInternalUser;
mockedClient.nodes.info.mockImplementation(() =>
diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts
index f345732c7a7c4..fddff84293140 100644
--- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts
@@ -120,6 +120,7 @@ export class ElasticsearchService
}
this.unauthorizedErrorHandler = handler;
},
+ agentStore: this.agentManager,
};
}
@@ -182,7 +183,7 @@ export class ElasticsearchService
authHeaders: this.authHeaders,
getExecutionContext: () => this.executionContextClient?.getAsHeader(),
getUnauthorizedErrorHandler: () => this.unauthorizedErrorHandler,
- agentManager: this.agentManager,
+ agentFactoryProvider: this.agentManager,
kibanaVersion: this.kibanaVersion,
});
}
diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts
index 8d05ad0c4cd0a..b03b86c7bdd1c 100644
--- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts
@@ -12,6 +12,7 @@ import type {
ElasticsearchServiceStart,
ElasticsearchServiceSetup,
} from '@kbn/core-elasticsearch-server';
+import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal';
import type { ServiceStatus } from '@kbn/core-status-common';
import type { NodesVersionCompatibility, NodeInfo } from './version_check/ensure_es_version';
import type { ClusterInfo } from './get_cluster_info';
@@ -21,6 +22,7 @@ export type InternalElasticsearchServicePreboot = ElasticsearchServicePreboot;
/** @internal */
export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup {
+ agentStore: AgentStore;
clusterInfo$: Observable;
esNodesCompatibility$: Observable;
status$: Observable>;
diff --git a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts
index a1323be0ea71b..26d81da24318c 100644
--- a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts
@@ -13,6 +13,7 @@ import {
elasticsearchClientMock,
type ClusterClientMock,
type CustomClusterClientMock,
+ createAgentStoreMock,
} from '@kbn/core-elasticsearch-client-server-mocks';
import type {
ElasticsearchClientConfig,
@@ -94,6 +95,7 @@ const createInternalSetupContractMock = () => {
level: ServiceStatusLevels.available,
summary: 'Elasticsearch is available',
}),
+ agentStore: createAgentStoreMock(),
};
return internalSetupContract;
};
diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts
index 57eadf70ef68a..a8e065d357ee1 100644
--- a/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts
+++ b/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts
@@ -7,8 +7,8 @@
*/
import type { ElasticsearchClient } from './client';
-import { ScopeableRequest } from './scopeable_request';
-import { IScopedClusterClient } from './scoped_cluster_client';
+import type { ScopeableRequest } from './scopeable_request';
+import type { IScopedClusterClient } from './scoped_cluster_client';
/**
* Represents an Elasticsearch cluster API client created by the platform.
diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel b/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel
index 2b789e97cbe69..9761bcbf1cefb 100644
--- a/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel
+++ b/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel
@@ -39,6 +39,8 @@ RUNTIME_DEPS = [
"//packages/kbn-logging",
"@npm//moment",
"@npm//getos",
+ ### test dependencies
+ "//packages/core/elasticsearch/core-elasticsearch-client-server-mocks",
]
TYPES_DEPS = [
@@ -50,6 +52,7 @@ TYPES_DEPS = [
"@npm//@types/hapi__hapi",
"//packages/kbn-logging:npm_module_types",
"//packages/core/metrics/core-metrics-server:npm_module_types",
+ "//packages/core/elasticsearch/core-elasticsearch-client-server-internal:npm_module_types",
]
jsts_transpiler(
diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/index.ts b/packages/core/metrics/core-metrics-collectors-server-internal/index.ts
index a4639202353e1..351129cdc8ba3 100644
--- a/packages/core/metrics/core-metrics-collectors-server-internal/index.ts
+++ b/packages/core/metrics/core-metrics-collectors-server-internal/index.ts
@@ -11,3 +11,4 @@ export type { OpsMetricsCollectorOptions } from './src/os';
export { ProcessMetricsCollector } from './src/process';
export { ServerMetricsCollector } from './src/server';
export { EventLoopDelaysMonitor } from './src/event_loop_delays_monitor';
+export { ElasticsearchClientsMetricsCollector } from './src/elasticsearch_client';
diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts
new file mode 100644
index 0000000000000..363fca6430dbe
--- /dev/null
+++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.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 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 { Agent as HttpAgent } from 'http';
+import { Agent as HttpsAgent } from 'https';
+import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks';
+import { createAgentStoreMock } from '@kbn/core-elasticsearch-client-server-mocks';
+import { getAgentsSocketsStatsMock } from './get_agents_sockets_stats.test.mocks';
+import { ElasticsearchClientsMetricsCollector } from './elasticsearch_client';
+import { getAgentsSocketsStats } from './get_agents_sockets_stats';
+
+jest.mock('@kbn/core-elasticsearch-client-server-internal');
+
+describe('ElasticsearchClientsMetricsCollector', () => {
+ test('#collect calls getAgentsSocketsStats with the Agents managed by the provided AgentManager', async () => {
+ const agents = new Set([new HttpAgent(), new HttpsAgent()]);
+ const agentStore = createAgentStoreMock(agents);
+ getAgentsSocketsStatsMock.mockReturnValueOnce(sampleEsClientMetrics);
+
+ const esClientsMetricsCollector = new ElasticsearchClientsMetricsCollector(agentStore);
+ const metrics = await esClientsMetricsCollector.collect();
+
+ expect(agentStore.getAgents).toHaveBeenCalledTimes(1);
+ expect(getAgentsSocketsStats).toHaveBeenCalledTimes(1);
+ expect(getAgentsSocketsStats).toHaveBeenNthCalledWith(1, agents);
+ expect(metrics).toEqual(sampleEsClientMetrics);
+ });
+});
diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts
new file mode 100644
index 0000000000000..278fd0218f8c0
--- /dev/null
+++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { ElasticsearchClientsMetrics, MetricsCollector } from '@kbn/core-metrics-server';
+import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal';
+import { getAgentsSocketsStats } from './get_agents_sockets_stats';
+
+export class ElasticsearchClientsMetricsCollector
+ implements MetricsCollector
+{
+ constructor(private readonly agentStore: AgentStore) {}
+
+ public async collect(): Promise {
+ return await getAgentsSocketsStats(this.agentStore.getAgents());
+ }
+
+ public reset() {
+ // we do not have a state in this Collector, aka metrics are not accumulated over time.
+ // Thus, we don't need to perform any cleanup to reset the collected metrics
+ }
+}
diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts
new file mode 100644
index 0000000000000..4e9688ccc91b9
--- /dev/null
+++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { Agent as HttpAgent } from 'http';
+import { Agent as HttpsAgent } from 'https';
+
+import { getAgentsSocketsStats } from './get_agents_sockets_stats';
+
+export const getHttpAgentMock = (overrides: Partial) => {
+ return Object.assign(new HttpAgent(), overrides);
+};
+
+export const getHttpsAgentMock = (overrides: Partial) => {
+ return Object.assign(new HttpsAgent(), overrides);
+};
+
+export const getAgentsSocketsStatsMock: jest.MockedFunction =
+ jest.fn();
+
+jest.doMock('./get_agents_sockets_stats', () => {
+ return {
+ getAgentsSocketsStats: getAgentsSocketsStatsMock,
+ };
+});
diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts
new file mode 100644
index 0000000000000..513bf2caa8545
--- /dev/null
+++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts
@@ -0,0 +1,147 @@
+/*
+ * 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 { Socket } from 'net';
+import { Agent, IncomingMessage } from 'http';
+import { getAgentsSocketsStats } from './get_agents_sockets_stats';
+import { getHttpAgentMock, getHttpsAgentMock } from './get_agents_sockets_stats.test.mocks';
+
+jest.mock('net');
+
+const mockSocket = new Socket();
+const mockIncomingMessage = new IncomingMessage(mockSocket);
+
+describe('getAgentsSocketsStats()', () => {
+ it('extracts aggregated stats from the specified agents', () => {
+ const agent1 = getHttpAgentMock({
+ sockets: {
+ node1: [mockSocket, mockSocket, mockSocket],
+ node2: [mockSocket],
+ },
+ freeSockets: {
+ node1: [mockSocket],
+ node3: [mockSocket, mockSocket, mockSocket, mockSocket],
+ },
+ requests: {
+ node1: [mockIncomingMessage, mockIncomingMessage],
+ },
+ });
+
+ const agent2 = getHttpAgentMock({
+ sockets: {
+ node1: [mockSocket, mockSocket, mockSocket],
+ node4: [mockSocket],
+ },
+ freeSockets: {
+ node3: [mockSocket, mockSocket, mockSocket, mockSocket],
+ },
+ requests: {
+ node4: [mockIncomingMessage, mockIncomingMessage, mockIncomingMessage, mockIncomingMessage],
+ },
+ });
+
+ const stats = getAgentsSocketsStats(new Set([agent1, agent2]));
+ expect(stats).toEqual({
+ averageActiveSocketsPerNode: 2.6666666666666665,
+ averageIdleSocketsPerNode: 4.5,
+ connectedNodes: 4,
+ mostActiveNodeSockets: 6,
+ mostIdleNodeSockets: 8,
+ nodesWithActiveSockets: 3,
+ nodesWithIdleSockets: 2,
+ protocol: 'http',
+ totalActiveSockets: 8,
+ totalIdleSockets: 9,
+ totalQueuedRequests: 6,
+ });
+ });
+
+ it('takes into account Agent types to determine the `protocol`', () => {
+ const httpAgent = getHttpAgentMock({
+ sockets: { node1: [mockSocket] },
+ freeSockets: {},
+ requests: {},
+ });
+
+ const httpsAgent = getHttpsAgentMock({
+ sockets: { node1: [mockSocket] },
+ freeSockets: {},
+ requests: {},
+ });
+
+ const noAgents = new Set();
+ const httpAgents = new Set([httpAgent, httpAgent]);
+ const httpsAgents = new Set([httpsAgent, httpsAgent]);
+ const mixedAgents = new Set([httpAgent, httpsAgent]);
+
+ expect(getAgentsSocketsStats(noAgents).protocol).toEqual('none');
+ expect(getAgentsSocketsStats(httpAgents).protocol).toEqual('http');
+ expect(getAgentsSocketsStats(httpsAgents).protocol).toEqual('https');
+ expect(getAgentsSocketsStats(mixedAgents).protocol).toEqual('mixed');
+ });
+
+ it('does not take into account those Agents that have not had any connection to any node', () => {
+ const pristineAgentProps = {
+ sockets: {},
+ freeSockets: {},
+ requests: {},
+ };
+ const agent1 = getHttpAgentMock(pristineAgentProps);
+ const agent2 = getHttpAgentMock(pristineAgentProps);
+ const agent3 = getHttpAgentMock(pristineAgentProps);
+
+ const stats = getAgentsSocketsStats(new Set([agent1, agent2, agent3]));
+
+ expect(stats).toEqual({
+ averageActiveSocketsPerNode: 0,
+ averageIdleSocketsPerNode: 0,
+ connectedNodes: 0,
+ mostActiveNodeSockets: 0,
+ mostIdleNodeSockets: 0,
+ nodesWithActiveSockets: 0,
+ nodesWithIdleSockets: 0,
+ protocol: 'none',
+ totalActiveSockets: 0,
+ totalIdleSockets: 0,
+ totalQueuedRequests: 0,
+ });
+ });
+
+ it('takes into account those Agents that have hold mappings to one or more nodes, but that do not currently have any pending requests, active connections or idle connections', () => {
+ const emptyAgentProps = {
+ sockets: {
+ node1: [],
+ },
+ freeSockets: {
+ node2: [],
+ },
+ requests: {
+ node3: [],
+ },
+ };
+
+ const agent1 = getHttpAgentMock(emptyAgentProps);
+ const agent2 = getHttpAgentMock(emptyAgentProps);
+
+ const stats = getAgentsSocketsStats(new Set([agent1, agent2]));
+
+ expect(stats).toEqual({
+ averageActiveSocketsPerNode: 0,
+ averageIdleSocketsPerNode: 0,
+ connectedNodes: 3,
+ mostActiveNodeSockets: 0,
+ mostIdleNodeSockets: 0,
+ nodesWithActiveSockets: 0,
+ nodesWithIdleSockets: 0,
+ protocol: 'http',
+ totalActiveSockets: 0,
+ totalIdleSockets: 0,
+ totalQueuedRequests: 0,
+ });
+ });
+});
diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts
new file mode 100644
index 0000000000000..e28c92a56a8a4
--- /dev/null
+++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts
@@ -0,0 +1,81 @@
+/*
+ * 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 { NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal';
+import { Agent as HttpsAgent } from 'https';
+import { mean } from 'lodash';
+import type {
+ ElasticsearchClientProtocol,
+ ElasticsearchClientsMetrics,
+} from '@kbn/core-metrics-server';
+
+export const getAgentsSocketsStats = (agents: Set): ElasticsearchClientsMetrics => {
+ const nodes = new Set();
+ let totalActiveSockets = 0;
+ let totalIdleSockets = 0;
+ let totalQueuedRequests = 0;
+ let http: boolean = false;
+ let https: boolean = false;
+
+ const nodesWithActiveSockets: Record = {};
+ const nodesWithIdleSockets: Record = {};
+
+ agents.forEach((agent) => {
+ const agentRequests = Object.entries(agent.requests) ?? [];
+ const agentSockets = Object.entries(agent.sockets) ?? [];
+ const agentFreeSockets = Object.entries(agent.freeSockets) ?? [];
+
+ if (agentRequests.length || agentSockets.length || agentFreeSockets.length) {
+ if (agent instanceof HttpsAgent) https = true;
+ else http = true;
+
+ agentRequests.forEach(([node, queue]) => {
+ nodes.add(node);
+ totalQueuedRequests += queue?.length ?? 0;
+ });
+
+ agentSockets.forEach(([node, sockets]) => {
+ nodes.add(node);
+ const activeSockets = sockets?.length ?? 0;
+ totalActiveSockets += activeSockets;
+ nodesWithActiveSockets[node] = (nodesWithActiveSockets[node] ?? 0) + activeSockets;
+ });
+
+ agentFreeSockets.forEach(([node, freeSockets]) => {
+ nodes.add(node);
+ const idleSockets = freeSockets?.length ?? 0;
+ totalIdleSockets += idleSockets;
+ nodesWithIdleSockets[node] = (nodesWithIdleSockets[node] ?? 0) + idleSockets;
+ });
+ }
+ });
+
+ const activeSocketCounters = Object.values(nodesWithActiveSockets);
+ const idleSocketCounters = Object.values(nodesWithIdleSockets);
+ const protocol: ElasticsearchClientProtocol = http
+ ? https
+ ? 'mixed'
+ : 'http'
+ : https
+ ? 'https'
+ : 'none';
+
+ return {
+ protocol,
+ connectedNodes: nodes.size,
+ nodesWithActiveSockets: activeSocketCounters.filter(Boolean).length,
+ nodesWithIdleSockets: idleSocketCounters.filter(Boolean).length,
+ totalActiveSockets,
+ totalIdleSockets,
+ totalQueuedRequests,
+ mostActiveNodeSockets: activeSocketCounters.length ? Math.max(...activeSocketCounters) : 0,
+ averageActiveSocketsPerNode: activeSocketCounters.length ? mean(activeSocketCounters) : 0,
+ mostIdleNodeSockets: idleSocketCounters.length ? Math.max(...idleSocketCounters) : 0,
+ averageIdleSocketsPerNode: idleSocketCounters.length ? mean(idleSocketCounters) : 0,
+ };
+};
diff --git a/packages/core/metrics/core-metrics-server-internal/BUILD.bazel b/packages/core/metrics/core-metrics-server-internal/BUILD.bazel
index da7883016afd2..0a7f393ec0b31 100644
--- a/packages/core/metrics/core-metrics-server-internal/BUILD.bazel
+++ b/packages/core/metrics/core-metrics-server-internal/BUILD.bazel
@@ -37,11 +37,15 @@ NPM_MODULE_EXTRA_FILES = [
RUNTIME_DEPS = [
"@npm//rxjs",
"@npm//moment",
- "//packages/kbn-logging-mocks",
"//packages/kbn-config-schema",
- "//packages/core/http/core-http-server-mocks",
"//packages/core/metrics/core-metrics-collectors-server-internal",
+ "//packages/core/elasticsearch/core-elasticsearch-server-internal",
+ ### test dependencies
+ "//packages/kbn-logging-mocks",
+ "//packages/core/http/core-http-server-mocks",
+ "//packages/core/metrics/core-metrics-server-mocks",
"//packages/core/metrics/core-metrics-collectors-server-mocks",
+ "//packages/core/elasticsearch/core-elasticsearch-server-mocks",
]
@@ -57,6 +61,7 @@ TYPES_DEPS = [
"//packages/core/http/core-http-server-internal:npm_module_types",
"//packages/core/metrics/core-metrics-server:npm_module_types",
"//packages/core/metrics/core-metrics-collectors-server-internal:npm_module_types",
+ "//packages/core/elasticsearch/core-elasticsearch-server-internal:npm_module_types",
]
diff --git a/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts b/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts
index 8de7a5fa5dadf..d997433667e27 100644
--- a/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts
+++ b/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts
@@ -8,6 +8,7 @@
import type { OpsMetrics } from '@kbn/core-metrics-server';
import { getEcsOpsMetricsLog } from './get_ops_metrics_log';
+import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks';
import { collectorMock } from '@kbn/core-metrics-collectors-server-mocks';
function createBaseOpsMetrics(): OpsMetrics {
@@ -24,6 +25,7 @@ function createBaseOpsMetrics(): OpsMetrics {
memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 },
uptime_in_millis: 1,
},
+ elasticsearch_client: sampleEsClientMetrics,
response_times: { avg_in_millis: 1, max_in_millis: 1 },
requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } },
concurrent_connections: 1,
diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts
index de78b534b2dc7..351e2aca43f56 100644
--- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts
+++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts
@@ -8,13 +8,15 @@
import moment from 'moment';
+import { take } from 'rxjs/operators';
import { configServiceMock } from '@kbn/config-mocks';
import { mockCoreContext } from '@kbn/core-base-server-mocks';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { httpServiceMock } from '@kbn/core-http-server-mocks';
+import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
import { mockOpsCollector } from './metrics_service.test.mocks';
import { MetricsService } from './metrics_service';
-import { take } from 'rxjs/operators';
+import { OpsMetricsCollector } from './ops_metrics_collector';
const testInterval = 100;
@@ -24,6 +26,7 @@ const logger = loggingSystemMock.create();
describe('MetricsService', () => {
const httpMock = httpServiceMock.createInternalSetupContract();
+ const esServiceMock = elasticsearchServiceMock.createInternalSetup();
let metricsService: MetricsService;
beforeEach(() => {
@@ -43,9 +46,16 @@ describe('MetricsService', () => {
describe('#start', () => {
it('invokes setInterval with the configured interval', async () => {
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
await metricsService.start();
+ expect(OpsMetricsCollector).toHaveBeenCalledTimes(1);
+ expect(OpsMetricsCollector).toHaveBeenCalledWith(
+ httpMock.server,
+ esServiceMock.agentStore,
+ expect.objectContaining({ logger: logger.get('metrics') })
+ );
+
expect(setInterval).toHaveBeenCalledTimes(1);
expect(setInterval).toHaveBeenCalledWith(expect.any(Function), testInterval);
});
@@ -53,7 +63,7 @@ describe('MetricsService', () => {
it('collects the metrics at every interval', async () => {
mockOpsCollector.collect.mockResolvedValue(dummyMetrics);
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
await metricsService.start();
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1);
@@ -68,7 +78,7 @@ describe('MetricsService', () => {
it('resets the collector after each collection', async () => {
mockOpsCollector.collect.mockResolvedValue(dummyMetrics);
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
const { getOpsMetrics$ } = await metricsService.start();
// `advanceTimersByTime` only ensure the interval handler is executed
@@ -108,7 +118,7 @@ describe('MetricsService', () => {
.mockResolvedValueOnce(firstMetrics)
.mockResolvedValueOnce(secondMetrics);
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
const { getOpsMetrics$ } = await metricsService.start();
const nextEmission = async () => {
@@ -157,7 +167,7 @@ describe('MetricsService', () => {
mockOpsCollector.collect
.mockResolvedValueOnce(firstMetrics)
.mockResolvedValueOnce(secondMetrics);
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
const { getOpsMetrics$ } = await metricsService.start();
const nextEmission = async () => {
@@ -176,7 +186,7 @@ describe('MetricsService', () => {
it('omits metrics from log message if they are missing or malformed', async () => {
const opsLogger = logger.get('metrics', 'ops');
mockOpsCollector.collect.mockResolvedValueOnce({ secondMetrics: 'metrics' });
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
await metricsService.start();
expect(loggingSystemMock.collect(opsLogger).debug[0]).toMatchInlineSnapshot(`
Array [
@@ -219,7 +229,7 @@ describe('MetricsService', () => {
describe('#stop', () => {
it('stops the metrics interval', async () => {
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
const { getOpsMetrics$ } = await metricsService.start();
expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1);
@@ -235,7 +245,7 @@ describe('MetricsService', () => {
});
it('completes the metrics observable', async () => {
- await metricsService.setup({ http: httpMock });
+ await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock });
const { getOpsMetrics$ } = await metricsService.start();
let completed = false;
diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts
index 8a05b4b57843c..95a9dc09bba57 100644
--- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts
+++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts
@@ -10,6 +10,7 @@ import { firstValueFrom, ReplaySubject } from 'rxjs';
import type { CoreContext, CoreService } from '@kbn/core-base-server-internal';
import type { Logger } from '@kbn/logging';
import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal';
+import type { InternalElasticsearchServiceSetup } from '@kbn/core-elasticsearch-server-internal';
import type {
OpsMetrics,
MetricsServiceSetup,
@@ -21,6 +22,7 @@ import { getEcsOpsMetricsLog } from './logging';
export interface MetricsServiceSetupDeps {
http: InternalHttpServiceSetup;
+ elasticsearchService: InternalElasticsearchServiceSetup;
}
/** @internal */
@@ -45,12 +47,15 @@ export class MetricsService
this.opsMetricsLogger = coreContext.logger.get('metrics', 'ops');
}
- public async setup({ http }: MetricsServiceSetupDeps): Promise {
+ public async setup({
+ http,
+ elasticsearchService,
+ }: MetricsServiceSetupDeps): Promise {
const config = await firstValueFrom(
this.coreContext.configService.atPath(OPS_CONFIG_PATH)
);
- this.metricsCollector = new OpsMetricsCollector(http.server, {
+ this.metricsCollector = new OpsMetricsCollector(http.server, elasticsearchService.agentStore, {
logger: this.logger,
...config.cGroupOverrides,
});
diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts
index b96449fdc2f64..d70753b9f4644 100644
--- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts
+++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts
@@ -11,11 +11,13 @@ import { collectorMock } from '@kbn/core-metrics-collectors-server-mocks';
export const mockOsCollector = collectorMock.create();
export const mockProcessCollector = collectorMock.create();
export const mockServerCollector = collectorMock.create();
+export const mockEsClientCollector = collectorMock.create();
jest.doMock('@kbn/core-metrics-collectors-server-internal', () => {
return {
OsMetricsCollector: jest.fn().mockImplementation(() => mockOsCollector),
ProcessMetricsCollector: jest.fn().mockImplementation(() => mockProcessCollector),
ServerMetricsCollector: jest.fn().mockImplementation(() => mockServerCollector),
+ ElasticsearchClientsMetricsCollector: jest.fn().mockImplementation(() => mockEsClientCollector),
};
});
diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts
index cd80c35b37f86..87011a663404f 100644
--- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts
+++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts
@@ -8,7 +8,10 @@
import { loggerMock } from '@kbn/logging-mocks';
import { httpServiceMock } from '@kbn/core-http-server-mocks';
+import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks';
+import { AgentManager } from '@kbn/core-elasticsearch-client-server-internal';
import {
+ mockEsClientCollector,
mockOsCollector,
mockProcessCollector,
mockServerCollector,
@@ -20,7 +23,8 @@ describe('OpsMetricsCollector', () => {
beforeEach(() => {
const hapiServer = httpServiceMock.createInternalSetupContract().server;
- collector = new OpsMetricsCollector(hapiServer, { logger: loggerMock.create() });
+ const agentManager = new AgentManager();
+ collector = new OpsMetricsCollector(hapiServer, agentManager, { logger: loggerMock.create() });
mockOsCollector.collect.mockResolvedValue('osMetrics');
});
@@ -33,12 +37,14 @@ describe('OpsMetricsCollector', () => {
requests: 'serverRequestsMetrics',
response_times: 'serverTimingMetrics',
});
+ mockEsClientCollector.collect.mockResolvedValue(sampleEsClientMetrics);
const metrics = await collector.collect();
expect(mockOsCollector.collect).toHaveBeenCalledTimes(1);
expect(mockProcessCollector.collect).toHaveBeenCalledTimes(1);
expect(mockServerCollector.collect).toHaveBeenCalledTimes(1);
+ expect(mockEsClientCollector.collect).toHaveBeenCalledTimes(1);
expect(metrics).toEqual({
collected_at: expect.any(Date),
@@ -47,6 +53,7 @@ describe('OpsMetricsCollector', () => {
os: 'osMetrics',
requests: 'serverRequestsMetrics',
response_times: 'serverTimingMetrics',
+ elasticsearch_client: sampleEsClientMetrics,
});
});
});
diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts
index 10958d93c2562..8a10f4071b11b 100644
--- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts
+++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts
@@ -8,28 +8,33 @@
import { Server as HapiServer } from '@hapi/hapi';
import type { OpsMetrics, MetricsCollector } from '@kbn/core-metrics-server';
+import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal';
import {
ProcessMetricsCollector,
OsMetricsCollector,
type OpsMetricsCollectorOptions,
ServerMetricsCollector,
+ ElasticsearchClientsMetricsCollector,
} from '@kbn/core-metrics-collectors-server-internal';
export class OpsMetricsCollector implements MetricsCollector {
private readonly processCollector: ProcessMetricsCollector;
private readonly osCollector: OsMetricsCollector;
private readonly serverCollector: ServerMetricsCollector;
+ private readonly esClientCollector: ElasticsearchClientsMetricsCollector;
- constructor(server: HapiServer, opsOptions: OpsMetricsCollectorOptions) {
+ constructor(server: HapiServer, agentStore: AgentStore, opsOptions: OpsMetricsCollectorOptions) {
this.processCollector = new ProcessMetricsCollector();
this.osCollector = new OsMetricsCollector(opsOptions);
this.serverCollector = new ServerMetricsCollector(server);
+ this.esClientCollector = new ElasticsearchClientsMetricsCollector(agentStore);
}
public async collect(): Promise {
- const [processes, os, server] = await Promise.all([
+ const [processes, os, esClient, server] = await Promise.all([
this.processCollector.collect(),
this.osCollector.collect(),
+ this.esClientCollector.collect(),
this.serverCollector.collect(),
]);
@@ -43,6 +48,7 @@ export class OpsMetricsCollector implements MetricsCollector {
process: processes[0],
processes,
os,
+ elasticsearch_client: esClient,
...server,
};
}
diff --git a/packages/core/metrics/core-metrics-server-mocks/index.ts b/packages/core/metrics/core-metrics-server-mocks/index.ts
index d252b2253243e..02d13b8ed5ad8 100644
--- a/packages/core/metrics/core-metrics-server-mocks/index.ts
+++ b/packages/core/metrics/core-metrics-server-mocks/index.ts
@@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
-export { metricsServiceMock } from './src/metrics_service.mock';
+export { metricsServiceMock, sampleEsClientMetrics } from './src/metrics_service.mock';
diff --git a/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts b/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts
index 6bbe176ce37e8..44601caeaa85c 100644
--- a/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts
+++ b/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts
@@ -17,7 +17,25 @@ import {
mocked as eventLoopDelaysMonitorMock,
collectorMock,
} from '@kbn/core-metrics-collectors-server-mocks';
-import type { MetricsServiceSetup, MetricsServiceStart } from '@kbn/core-metrics-server';
+import type {
+ ElasticsearchClientsMetrics,
+ MetricsServiceSetup,
+ MetricsServiceStart,
+} from '@kbn/core-metrics-server';
+
+export const sampleEsClientMetrics: ElasticsearchClientsMetrics = {
+ protocol: 'https',
+ connectedNodes: 3,
+ nodesWithActiveSockets: 3,
+ nodesWithIdleSockets: 1,
+ totalActiveSockets: 25,
+ totalIdleSockets: 2,
+ totalQueuedRequests: 0,
+ mostActiveNodeSockets: 15,
+ averageActiveSocketsPerNode: 8,
+ mostIdleNodeSockets: 2,
+ averageIdleSocketsPerNode: 0.5,
+};
const createInternalSetupContractMock = () => {
const setupContract: jest.Mocked = {
@@ -39,6 +57,7 @@ const createInternalSetupContractMock = () => {
memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 },
uptime_in_millis: 1,
},
+ elasticsearch_client: sampleEsClientMetrics,
response_times: { avg_in_millis: 1, max_in_millis: 1 },
requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } },
concurrent_connections: 1,
diff --git a/packages/core/metrics/core-metrics-server/index.ts b/packages/core/metrics/core-metrics-server/index.ts
index 51e0b7fe3d95d..49bd2a4251623 100644
--- a/packages/core/metrics/core-metrics-server/index.ts
+++ b/packages/core/metrics/core-metrics-server/index.ts
@@ -14,4 +14,6 @@ export type {
OpsProcessMetrics,
OpsOsMetrics,
OpsServerMetrics,
+ ElasticsearchClientProtocol,
+ ElasticsearchClientsMetrics,
} from './src/metrics';
diff --git a/packages/core/metrics/core-metrics-server/src/metrics.ts b/packages/core/metrics/core-metrics-server/src/metrics.ts
index dbfa643c8eccc..958f6b75f55e4 100644
--- a/packages/core/metrics/core-metrics-server/src/metrics.ts
+++ b/packages/core/metrics/core-metrics-server/src/metrics.ts
@@ -40,6 +40,44 @@ export interface IntervalHistogram {
};
}
+/**
+ * Protocol(s) used by the Elasticsearch Client
+ * @public
+ */
+
+export type ElasticsearchClientProtocol = 'none' | 'http' | 'https' | 'mixed';
+
+/**
+ * Metrics related to the elasticsearch clients
+ * @public
+ */
+export interface ElasticsearchClientsMetrics {
+ /** The protocol (or protocols) that these Agents are using */
+ protocol: ElasticsearchClientProtocol;
+ /** Number of ES nodes that ES-js client is connecting to */
+ connectedNodes: number;
+ /** Number of nodes with active connections */
+ nodesWithActiveSockets: number;
+ /** Number of nodes with available connections (alive but idle).
+ * Note that a node can have both active and idle connections at the same time
+ */
+ nodesWithIdleSockets: number;
+ /** Total number of active sockets (all nodes, all connections) */
+ totalActiveSockets: number;
+ /** Total number of available sockets (alive but idle, all nodes, all connections) */
+ totalIdleSockets: number;
+ /** Total number of queued requests (all nodes, all connections) */
+ totalQueuedRequests: number;
+ /** Number of active connections of the node with most active connections */
+ mostActiveNodeSockets: number;
+ /** Average of active sockets per node (all connections) */
+ averageActiveSocketsPerNode: number;
+ /** Number of idle connections of the node with most idle connections */
+ mostIdleNodeSockets: number;
+ /** Average of available (idle) sockets per node (all connections) */
+ averageIdleSocketsPerNode: number;
+}
+
/**
* Process related metrics
* @public
@@ -165,6 +203,10 @@ export interface OpsServerMetrics {
export interface OpsMetrics {
/** Time metrics were recorded at. */
collected_at: Date;
+ /**
+ * Metrics related to the elasticsearch client
+ */
+ elasticsearch_client: ElasticsearchClientsMetrics;
/**
* Process related metrics.
* @deprecated use the processes field instead.
diff --git a/packages/core/status/core-status-server-internal/src/routes/status.ts b/packages/core/status/core-status-server-internal/src/routes/status.ts
index 34a5a9b4dcd20..199f55159a7c6 100644
--- a/packages/core/status/core-status-server-internal/src/routes/status.ts
+++ b/packages/core/status/core-status-server-internal/src/routes/status.ts
@@ -135,6 +135,7 @@ export const registerStatusRoute = ({
...lastMetrics.requests,
status_codes: lastMetrics.requests.statusCodes,
},
+ elasticsearch_client: lastMetrics.elasticsearch_client,
},
};
diff --git a/src/cli_setup/utils.ts b/src/cli_setup/utils.ts
index 5c66fa84c0f30..47b8199f16ea0 100644
--- a/src/cli_setup/utils.ts
+++ b/src/cli_setup/utils.ts
@@ -48,7 +48,7 @@ export const elasticsearch = new ElasticsearchService(logger, kibanaPackageJson.
logger,
type,
// we use an independent AgentManager for cli_setup, no need to track performance of this one
- agentManager: new AgentManager(),
+ agentFactoryProvider: new AgentManager(),
kibanaVersion: kibanaPackageJson.version,
});
},
diff --git a/src/core/server/server.ts b/src/core/server/server.ts
index b7f41dd31dd04..e0a2c2c44f254 100644
--- a/src/core/server/server.ts
+++ b/src/core/server/server.ts
@@ -280,7 +280,10 @@ export class Server {
executionContext: executionContextSetup,
});
- const metricsSetup = await this.metrics.setup({ http: httpSetup });
+ const metricsSetup = await this.metrics.setup({
+ http: httpSetup,
+ elasticsearchService: elasticsearchServiceSetup,
+ });
const coreUsageDataSetup = this.coreUsageData.setup({
http: httpSetup,
diff --git a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap
index f962eca858199..d77d43293480b 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap
+++ b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap
@@ -3,6 +3,19 @@
exports[`telemetry_ops_stats should return something when there is a metric 1`] = `
Object {
"concurrent_connections": 1,
+ "elasticsearch_client": Object {
+ "averageActiveSocketsPerNode": 8,
+ "averageIdleSocketsPerNode": 0.5,
+ "connectedNodes": 3,
+ "mostActiveNodeSockets": 15,
+ "mostIdleNodeSockets": 2,
+ "nodesWithActiveSockets": 3,
+ "nodesWithIdleSockets": 1,
+ "protocol": "https",
+ "totalActiveSockets": 25,
+ "totalIdleSockets": 2,
+ "totalQueuedRequests": 0,
+ },
"os": Object {
"load": Object {
"15m": 1,
From b81993d6d805c432e19472cf1117b2d5fbe297c0 Mon Sep 17 00:00:00 2001
From: Justin Kambic
Date: Tue, 4 Oct 2022 11:44:22 -0400
Subject: [PATCH 31/39] [Synthetics] Serialize errors before sending to redux
store (#142603)
* [Synthetics UI] Serialize errors before sending to redux store to prevent warnings (#142259)
* Serialize errors before sending to redux store to prevent warnings.
* Serialize response errors in monitor list effect.
* Revise reducer case to avoid type error.
---
.../public/apps/synthetics/state/index_status/actions.ts | 5 +++--
.../public/apps/synthetics/state/index_status/index.ts | 4 ++--
.../apps/synthetics/state/monitor_details/index.ts | 7 +++----
.../public/apps/synthetics/state/monitor_list/actions.ts | 9 +++++----
.../public/apps/synthetics/state/monitor_list/effects.ts | 4 ++--
.../public/apps/synthetics/state/monitor_list/index.ts | 4 ++--
.../public/apps/synthetics/state/overview/index.ts | 8 ++++----
.../apps/synthetics/state/service_locations/actions.ts | 5 ++++-
.../apps/synthetics/state/service_locations/index.ts | 3 ++-
.../synthetics/state/synthetics_enablement/actions.ts | 7 ++++---
.../apps/synthetics/state/synthetics_enablement/index.ts | 3 ++-
.../public/apps/synthetics/state/utils/actions.ts | 4 ++--
.../public/apps/synthetics/state/utils/fetch_effect.ts | 7 ++++---
.../legacy_uptime/state/private_locations/index.ts | 6 +++---
14 files changed, 42 insertions(+), 34 deletions(-)
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts
index 36e2e2514910e..e522af3bfed7c 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts
@@ -5,10 +5,11 @@
* 2.0.
*/
-import type { IHttpFetchError } from '@kbn/core-http-browser';
import { createAction } from '@reduxjs/toolkit';
import { StatesIndexStatus } from '../../../../../common/runtime_types';
+import { IHttpSerializedFetchError } from '../utils/http_error';
export const getIndexStatus = createAction('[INDEX STATUS] GET');
export const getIndexStatusSuccess = createAction('[INDEX STATUS] GET SUCCESS');
-export const getIndexStatusFail = createAction('[INDEX STATUS] GET FAIL');
+export const getIndexStatusFail =
+ createAction('[INDEX STATUS] GET FAIL');
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts
index f5351c65d0d6b..19ef8f94938a3 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts
@@ -6,7 +6,7 @@
*/
import { createReducer } from '@reduxjs/toolkit';
-import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error';
+import { IHttpSerializedFetchError } from '../utils/http_error';
import { StatesIndexStatus } from '../../../../../common/runtime_types';
import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './actions';
@@ -33,7 +33,7 @@ export const indexStatusReducer = createReducer(initialState, (builder) => {
state.loading = false;
})
.addCase(getIndexStatusFail, (state, action) => {
- state.error = serializeHttpFetchError(action.payload);
+ state.error = action.payload;
state.loading = false;
});
});
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts
index a2d9379df778e..b1fb95d5d5ee4 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts
@@ -5,9 +5,8 @@
* 2.0.
*/
-import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import { createReducer } from '@reduxjs/toolkit';
-import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error';
+import { IHttpSerializedFetchError } from '../utils/http_error';
import {
getMonitorRecentPingsAction,
setMonitorDetailsLocationAction,
@@ -47,7 +46,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => {
state.loading = false;
})
.addCase(getMonitorRecentPingsAction.fail, (state, action) => {
- state.error = serializeHttpFetchError(action.payload as IHttpFetchError);
+ state.error = action.payload;
state.loading = false;
})
@@ -59,7 +58,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => {
state.syntheticsMonitorLoading = false;
})
.addCase(getMonitorAction.fail, (state, action) => {
- state.error = serializeHttpFetchError(action.payload as IHttpFetchError);
+ state.error = action.payload;
state.syntheticsMonitorLoading = false;
});
});
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts
index fcfc3d4f22cf7..5a8c38284e034 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts
@@ -5,13 +5,13 @@
* 2.0.
*/
-import { IHttpFetchError } from '@kbn/core-http-browser';
import { createAction } from '@reduxjs/toolkit';
import {
EncryptedSyntheticsMonitor,
MonitorManagementListResult,
} from '../../../../../common/runtime_types';
import { createAsyncAction } from '../utils/actions';
+import { IHttpSerializedFetchError } from '../utils/http_error';
import { MonitorListPageState } from './models';
@@ -29,7 +29,8 @@ export const fetchUpsertSuccessAction = createAction<{
id: string;
attributes: { enabled: boolean };
}>('fetchUpsertMonitorSuccess');
-export const fetchUpsertFailureAction = createAction<{ id: string; error: IHttpFetchError }>(
- 'fetchUpsertMonitorFailure'
-);
+export const fetchUpsertFailureAction = createAction<{
+ id: string;
+ error: IHttpSerializedFetchError;
+}>('fetchUpsertMonitorFailure');
export const clearMonitorUpsertStatus = createAction('clearMonitorUpsertStatus');
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts
index 0dee2edfd7903..67aaa4ec982ed 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts
@@ -5,10 +5,10 @@
* 2.0.
*/
-import { IHttpFetchError } from '@kbn/core-http-browser';
import { PayloadAction } from '@reduxjs/toolkit';
import { call, put, takeEvery, takeLeading } from 'redux-saga/effects';
import { fetchEffectFactory } from '../utils/fetch_effect';
+import { serializeHttpFetchError } from '../utils/http_error';
import {
fetchMonitorListAction,
fetchUpsertFailureAction,
@@ -40,7 +40,7 @@ export function* upsertMonitorEffect() {
);
} catch (error) {
yield put(
- fetchUpsertFailureAction({ id: action.payload.id, error: error as IHttpFetchError })
+ fetchUpsertFailureAction({ id: action.payload.id, error: serializeHttpFetchError(error) })
);
}
}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts
index e1f564c0d0a3f..997f853c9bfc5 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts
@@ -10,7 +10,7 @@ import { FETCH_STATUS } from '@kbn/observability-plugin/public';
import { ConfigKey, MonitorManagementListResult } from '../../../../../common/runtime_types';
-import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error';
+import { IHttpSerializedFetchError } from '../utils/http_error';
import { MonitorListPageState } from './models';
import {
@@ -58,7 +58,7 @@ export const monitorListReducer = createReducer(initialState, (builder) => {
})
.addCase(fetchMonitorListAction.fail, (state, action) => {
state.loading = false;
- state.error = serializeHttpFetchError(action.payload);
+ state.error = action.payload;
})
.addCase(fetchUpsertMonitorAction, (state, action) => {
state.monitorUpsertStatuses[action.payload.id] = {
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts
index 49159b29ef461..82272638ffb11 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts
@@ -9,7 +9,7 @@ import { createReducer } from '@reduxjs/toolkit';
import { MonitorOverviewResult, OverviewStatus } from '../../../../../common/runtime_types';
-import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error';
+import { IHttpSerializedFetchError } from '../utils/http_error';
import { MonitorOverviewPageState } from './models';
import {
@@ -60,13 +60,13 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => {
})
.addCase(fetchMonitorOverviewAction.fail, (state, action) => {
state.loading = false;
- state.error = serializeHttpFetchError(action.payload);
+ state.error = action.payload;
})
.addCase(quietFetchOverviewAction.success, (state, action) => {
state.data = action.payload;
})
.addCase(quietFetchOverviewAction.fail, (state, action) => {
- state.error = serializeHttpFetchError(action.payload);
+ state.error = action.payload;
})
.addCase(setOverviewPerPageAction, (state, action) => {
state.pageState = {
@@ -79,7 +79,7 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => {
state.status = action.payload;
})
.addCase(fetchOverviewStatusAction.fail, (state, action) => {
- state.statusError = serializeHttpFetchError(action.payload);
+ state.statusError = action.payload;
})
.addCase(clearOverviewStatusErrorAction, (state) => {
state.statusError = null;
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts
index 794e16d0292c5..dbdd53d4cbcb7 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts
@@ -7,10 +7,13 @@
import { createAction } from '@reduxjs/toolkit';
import { ServiceLocations, ThrottlingOptions } from '../../../../../common/runtime_types';
+import { IHttpSerializedFetchError } from '../utils/http_error';
export const getServiceLocations = createAction('[SERVICE LOCATIONS] GET');
export const getServiceLocationsSuccess = createAction<{
throttling: ThrottlingOptions | undefined;
locations: ServiceLocations;
}>('[SERVICE LOCATIONS] GET SUCCESS');
-export const getServiceLocationsFailure = createAction('[SERVICE LOCATIONS] GET FAILURE');
+export const getServiceLocationsFailure = createAction(
+ '[SERVICE LOCATIONS] GET FAILURE'
+);
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts
index e13fe756ec7fd..9a338458e603f 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts
@@ -11,6 +11,7 @@ import {
ServiceLocations,
ThrottlingOptions,
} from '../../../../../common/runtime_types';
+import { IHttpSerializedFetchError } from '../utils/http_error';
import {
getServiceLocations,
@@ -22,7 +23,7 @@ export interface ServiceLocationsState {
locations: ServiceLocations;
throttling: ThrottlingOptions | null;
loading: boolean;
- error: Error | null;
+ error: IHttpSerializedFetchError | null;
locationsLoaded?: boolean;
}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts
index c38fadc0952a6..0c7abffd1b289 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts
@@ -7,23 +7,24 @@
import { createAction } from '@reduxjs/toolkit';
import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types';
+import { IHttpSerializedFetchError } from '../utils/http_error';
export const getSyntheticsEnablement = createAction('[SYNTHETICS_ENABLEMENT] GET');
export const getSyntheticsEnablementSuccess = createAction(
'[SYNTHETICS_ENABLEMENT] GET SUCCESS'
);
-export const getSyntheticsEnablementFailure = createAction(
+export const getSyntheticsEnablementFailure = createAction(
'[SYNTHETICS_ENABLEMENT] GET FAILURE'
);
export const disableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] DISABLE');
export const disableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] DISABLE SUCCESS');
-export const disableSyntheticsFailure = createAction(
+export const disableSyntheticsFailure = createAction(
'[SYNTHETICS_ENABLEMENT] DISABLE FAILURE'
);
export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE');
export const enableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS');
-export const enableSyntheticsFailure = createAction(
+export const enableSyntheticsFailure = createAction(
'[SYNTHETICS_ENABLEMENT] ENABLE FAILURE'
);
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts
index 62ed85ad17e86..3bf9ff69bf005 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts
@@ -18,10 +18,11 @@ import {
getSyntheticsEnablementFailure,
} from './actions';
import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types';
+import { IHttpSerializedFetchError } from '../utils/http_error';
export interface SyntheticsEnablementState {
loading: boolean;
- error: Error | null;
+ error: IHttpSerializedFetchError | null;
enablement: MonitorManagementEnablementResult | null;
}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts
index 416c3134d6034..35e93fd91484e 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts
@@ -6,13 +6,13 @@
*/
import { createAction } from '@reduxjs/toolkit';
-import type { IHttpFetchError } from '@kbn/core-http-browser';
+import type { IHttpSerializedFetchError } from './http_error';
export function createAsyncAction(actionStr: string) {
return {
get: createAction(actionStr),
success: createAction(`${actionStr}_SUCCESS`),
- fail: createAction(`${actionStr}_FAIL`),
+ fail: createAction(`${actionStr}_FAIL`),
};
}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts
index b07f1fa542633..294da718a6fd3 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts
@@ -8,6 +8,7 @@
import { call, put } from 'redux-saga/effects';
import { PayloadAction } from '@reduxjs/toolkit';
import type { IHttpFetchError } from '@kbn/core-http-browser';
+import { IHttpSerializedFetchError, serializeHttpFetchError } from './http_error';
/**
* Factory function for a fetch effect. It expects three action creators,
@@ -23,7 +24,7 @@ import type { IHttpFetchError } from '@kbn/core-http-browser';
export function fetchEffectFactory(
fetch: (request: T) => Promise,
success: (response: R) => PayloadAction,
- fail: (error: IHttpFetchError) => PayloadAction
+ fail: (error: IHttpSerializedFetchError) => PayloadAction
) {
return function* (action: PayloadAction): Generator {
try {
@@ -32,14 +33,14 @@ export function fetchEffectFactory(
// eslint-disable-next-line no-console
console.error(response);
- yield put(fail(response as IHttpFetchError));
+ yield put(fail(serializeHttpFetchError(response as IHttpFetchError)));
} else {
yield put(success(response as R));
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
- yield put(fail(error as IHttpFetchError));
+ yield put(fail(serializeHttpFetchError(error)));
}
};
}
diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts
index 0ff45023143ec..831f8a9cbf6bb 100644
--- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts
+++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
-import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import { createReducer } from '@reduxjs/toolkit';
import { AgentPolicy } from '@kbn/fleet-plugin/common';
+import { IHttpSerializedFetchError } from '../../../apps/synthetics/state';
import {
getAgentPoliciesAction,
setAddingNewPrivateLocation,
@@ -24,7 +24,7 @@ export interface AgentPoliciesList {
export interface AgentPoliciesState {
data: AgentPoliciesList | null;
loading: boolean;
- error: IHttpFetchError | null;
+ error: IHttpSerializedFetchError | null;
isManageFlyoutOpen?: boolean;
isAddingNewPrivateLocation?: boolean;
}
@@ -47,7 +47,7 @@ export const agentPoliciesReducer = createReducer(initialState, (builder) => {
state.loading = false;
})
.addCase(getAgentPoliciesAction.fail, (state, action) => {
- state.error = action.payload as IHttpFetchError;
+ state.error = action.payload;
state.loading = false;
})
.addCase(setManageFlyoutOpen, (state, action) => {
From 524363d9ff6774fb242d2a1923d91e43fd147501 Mon Sep 17 00:00:00 2001
From: Adam Demjen
Date: Tue, 4 Oct 2022 11:48:12 -0400
Subject: [PATCH 32/39] [8.6][ML Inference] Add ML inference failure handler
(#142488)
* Add ML Inference failure handler
---
.../common/ml_inference_pipeline/index.ts | 14 ++
.../create_pipeline_definitions.test.ts | 121 +++++++++---------
2 files changed, 71 insertions(+), 64 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts
index 3bc43fa14d7b2..00d893ba9abaa 100644
--- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts
+++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts
@@ -52,6 +52,20 @@ export const generateMlInferencePipelineBody = ({
},
model_id: model.model_id,
target_field: `ml.inference.${destinationField}`,
+ on_failure: [
+ {
+ append: {
+ field: '_source._ingest.inference_errors',
+ value: [
+ {
+ pipeline: pipelineName,
+ message: `Processor 'inference' in pipeline '${pipelineName}' failed with message '{{ _ingest.on_failure_message }}'`,
+ timestamp: '{{{ _ingest.timestamp }}}',
+ },
+ ],
+ },
+ },
+ ],
},
},
{
diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts
index 5abd7db73170b..183e27a765c2f 100644
--- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts
@@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+import { merge } from 'lodash';
import { ElasticsearchClient } from '@kbn/core/server';
@@ -41,7 +42,7 @@ describe('createIndexPipelineDefinitions util function', () => {
describe('formatMlPipelineBody util function', () => {
const pipelineName = 'ml-inference-my-ml-proc';
const modelId = 'my-model-id';
- let modelInputField = 'my-model-input-field';
+ const modelInputField = 'my-model-input-field';
const modelType = 'pytorch';
const inferenceConfigKey = 'my-model-type';
const modelTypes = ['pytorch', 'my-model-type'];
@@ -49,6 +50,55 @@ describe('formatMlPipelineBody util function', () => {
const sourceField = 'my-source-field';
const destField = 'my-dest-field';
+ const expectedResult = {
+ description: '',
+ processors: [
+ {
+ remove: {
+ field: `ml.inference.${destField}`,
+ ignore_missing: true,
+ },
+ },
+ {
+ inference: {
+ field_map: {
+ [sourceField]: modelInputField,
+ },
+ model_id: modelId,
+ target_field: `ml.inference.${destField}`,
+ on_failure: [
+ {
+ append: {
+ field: '_source._ingest.inference_errors',
+ value: [
+ {
+ pipeline: pipelineName,
+ message: `Processor 'inference' in pipeline '${pipelineName}' failed with message '{{ _ingest.on_failure_message }}'`,
+ timestamp: '{{{ _ingest.timestamp }}}',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ append: {
+ field: '_source._ingest.processors',
+ value: [
+ {
+ model_version: modelVersion,
+ pipeline: pipelineName,
+ processed_timestamp: '{{{ _ingest.timestamp }}}',
+ types: modelTypes,
+ },
+ ],
+ },
+ },
+ ],
+ version: 1,
+ };
+
const mockClient = {
ml: {
getTrainedModels: jest.fn(),
@@ -60,41 +110,6 @@ describe('formatMlPipelineBody util function', () => {
});
it('should return the pipeline body', async () => {
- const expectedResult = {
- description: '',
- processors: [
- {
- remove: {
- field: `ml.inference.${destField}`,
- ignore_missing: true,
- },
- },
- {
- inference: {
- field_map: {
- [sourceField]: modelInputField,
- },
- model_id: modelId,
- target_field: `ml.inference.${destField}`,
- },
- },
- {
- append: {
- field: '_source._ingest.processors',
- value: [
- {
- model_version: modelVersion,
- pipeline: pipelineName,
- processed_timestamp: '{{{ _ingest.timestamp }}}',
- types: modelTypes,
- },
- ],
- },
- },
- ],
- version: 1,
- };
-
const mockResponse = {
count: 1,
trained_model_configs: [
@@ -136,41 +151,19 @@ describe('formatMlPipelineBody util function', () => {
});
it('should insert a placeholder if model has no input fields', async () => {
- modelInputField = 'MODEL_INPUT_FIELD';
- const expectedResult = {
- description: '',
+ const expectedResultWithNoInputField = merge({}, expectedResult, {
processors: [
- {
- remove: {
- field: `ml.inference.${destField}`,
- ignore_missing: true,
- },
- },
+ {}, // append - we'll leave it untouched
{
inference: {
field_map: {
- [sourceField]: modelInputField,
+ [sourceField]: 'MODEL_INPUT_FIELD',
},
- model_id: modelId,
- target_field: `ml.inference.${destField}`,
- },
- },
- {
- append: {
- field: '_source._ingest.processors',
- value: [
- {
- model_version: modelVersion,
- pipeline: pipelineName,
- processed_timestamp: '{{{ _ingest.timestamp }}}',
- types: modelTypes,
- },
- ],
},
},
],
- version: 1,
- };
+ });
+
const mockResponse = {
count: 1,
trained_model_configs: [
@@ -193,7 +186,7 @@ describe('formatMlPipelineBody util function', () => {
destField,
mockClient as unknown as ElasticsearchClient
);
- expect(actualResult).toEqual(expectedResult);
+ expect(actualResult).toEqual(expectedResultWithNoInputField);
expect(mockClient.ml.getTrainedModels).toHaveBeenCalledTimes(1);
});
});
From 19734bc35e1f8ffd53d89a51e569a8c0acfadf71 Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 4 Oct 2022 12:01:32 -0400
Subject: [PATCH 33/39] Remove Minutes from connector scheduling frequency
dropdown (#142619)
---
.../components/search_index/connector/connector_scheduling.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx
index ca9a415c4c958..5438cd1a25f11 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx
@@ -177,6 +177,7 @@ export const ConnectorSchedulingComponent: React.FC = () => {
setScheduling({ ...scheduling, interval: expression });
setHasChanges(true);
}}
+ frequencyBlockList={['MINUTE']}
/>
From 1c25d93cd24c2404f6559ca0c276c546c0919d5b Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 4 Oct 2022 12:03:44 -0400
Subject: [PATCH 34/39] [Enterprise Search] Update copy for landing page
(#141979)
---
.../components/product_selector/product_selector.tsx | 2 +-
.../add_content_empty_prompt/add_content_empty_prompt.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx
index 01997959ec413..7be1264f2a9da 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx
@@ -71,7 +71,7 @@ export const ProductSelector: React.FC = ({
<>
= ({ title, butto
{i18n.translate('xpack.enterpriseSearch.emptyState.description', {
defaultMessage:
- 'An Elasticsearch index is where your content gets stored. Get started by creating an Elasticsearch index and selecting an ingestion method. Options include the Elastic web crawler, third party data integrations, or using Elasticsearch API endpoints.',
+ 'Your content is stored in an Elasticsearch index. Get started by creating an Elasticsearch index and selecting an ingestion method. Options include the Elastic web crawler, third party data integrations, or using Elasticsearch API endpoints.',
})}
From 4c18c0a25e0276883f9c78455e94a513633dac74 Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 4 Oct 2022 12:05:21 -0400
Subject: [PATCH 35/39] Update copy for authentication header form (#142620)
---
.../authentication_panel_edit_content.tsx | 13 +++++++++++--
.../authentication_panel/constants.ts | 2 +-
.../public/applications/shared/constants/labels.ts | 4 ----
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx
index eeed5bb377fe7..8c7522dc868b4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx
@@ -21,7 +21,9 @@ import {
EuiFlexItem,
} from '@elastic/eui';
-import { USERNAME_LABEL, PASSWORD_LABEL, TOKEN_LABEL } from '../../../../shared/constants';
+import { i18n } from '@kbn/i18n';
+
+import { USERNAME_LABEL, PASSWORD_LABEL } from '../../../../shared/constants';
import { AuthenticationPanelLogic } from './authentication_panel_logic';
import { AUTHENTICATION_LABELS } from './constants';
@@ -82,7 +84,14 @@ export const AuthenticationPanelEditContent: React.FC = () => {
onChange={() => selectAuthOption('raw')}
>
-
+
Date: Tue, 4 Oct 2022 18:11:10 +0200
Subject: [PATCH 36/39] fix: handle the `undefined` case correctly (#142580)
---
...elated_alerts_by_process_ancestry.test.tsx | 21 ++++++++++++++++++-
.../related_alerts_by_process_ancestry.tsx | 9 ++++----
.../use_alert_prevalence_from_process_tree.ts | 2 +-
3 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx
index f95bf9234cc16..bd91f55d704da 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx
@@ -116,7 +116,7 @@ describe('RelatedAlertsByProcessAncestry', () => {
});
});
- it('renders a special message when there are no alerts to display', async () => {
+ it('renders a special message when there are no alerts to display (empty response)', async () => {
mockUseAlertPrevalenceFromProcessTree.mockReturnValue({
loading: false,
error: false,
@@ -134,4 +134,23 @@ describe('RelatedAlertsByProcessAncestry', () => {
expect(screen.getByText(PROCESS_ANCESTRY_EMPTY)).toBeInTheDocument();
});
});
+
+ it('renders a special message when there are no alerts to display (undefined case)', async () => {
+ mockUseAlertPrevalenceFromProcessTree.mockReturnValue({
+ loading: false,
+ error: false,
+ alertIds: undefined,
+ });
+
+ render(
+
+
+
+ );
+
+ userEvent.click(screen.getByText(PROCESS_ANCESTRY));
+ await waitFor(() => {
+ expect(screen.getByText(PROCESS_ANCESTRY_EMPTY)).toBeInTheDocument();
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx
index 330cb7ae113b3..28737c60f4e07 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx
@@ -70,15 +70,12 @@ export const RelatedAlertsByProcessAncestry = React.memo(
const [cache, setCache] = useState>({});
const onToggle = useCallback((isOpen: boolean) => setShowContent(isOpen), []);
- const isEmpty = !!cache.alertIds && cache.alertIds.length === 0;
// Makes sure the component is not fetching data before the accordion
// has been openend.
const renderContent = useCallback(() => {
if (!showContent) {
return null;
- } else if (isEmpty) {
- return PROCESS_ANCESTRY_EMPTY;
} else if (cache.alertIds) {
return (
(
onCacheLoad={setCache}
/>
);
- }, [showContent, cache, data, eventId, timelineId, index, originalDocumentId, isEmpty]);
+ }, [showContent, cache, data, eventId, timelineId, index, originalDocumentId]);
return (
{
- if (alertIds) {
+ if (alertIds && alertIds.length !== 0) {
onCacheLoad({ alertIds });
}
}, [alertIds, onCacheLoad]);
@@ -152,6 +149,8 @@ const FetchAndNotifyCachedAlertsByProcessAncestry: React.FC<{
return ;
} else if (error) {
return <>{PROCESS_ANCESTRY_ERROR}>;
+ } else if (!alertIds || alertIds.length === 0) {
+ return <>{PROCESS_ANCESTRY_EMPTY}>;
}
return null;
diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts
index 1a59271614c57..e3bc22ec2decb 100644
--- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts
@@ -18,7 +18,7 @@ interface UserAlertPrevalenceFromProcessTreeResult {
}
interface ProcessTreeAlertPrevalenceResponse {
- alertIds: string[];
+ alertIds: string[] | undefined;
}
interface EntityResponse {
From 43bbbc6b27e62fabec14cf67b7fdc70db6b16322 Mon Sep 17 00:00:00 2001
From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com>
Date: Tue, 4 Oct 2022 09:23:48 -0700
Subject: [PATCH 37/39] [RAM] Fix rule details page not displaying the rule
snooze status properly (#142292)
* Fix rule details not displaying rule snooze status
* Unit and E2E tests
* Fix test
* Fix test
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../alerting/server/routes/resolve_rule.ts | 2 +-
.../server/rules_client/rules_client.ts | 6 ++-
.../server/rules_client/tests/resolve.test.ts | 52 ++++++++++++++++++
.../apps/triggers_actions_ui/details.ts | 54 +++++++++++++++++--
4 files changed, 108 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts
index cde747f9272fe..b3576c0c5ed44 100644
--- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts
+++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts
@@ -75,7 +75,7 @@ export const resolveRuleRoute = (
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const { id } = req.params;
- const rule = await rulesClient.resolve({ id });
+ const rule = await rulesClient.resolve({ id, includeSnoozeData: true });
return res.ok({
body: rewriteBodyRes(rule),
});
diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts
index 7374868a11dbb..d08f12f054a50 100644
--- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts
+++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts
@@ -715,9 +715,11 @@ export class RulesClient {
public async resolve({
id,
includeLegacyId,
+ includeSnoozeData = false,
}: {
id: string;
includeLegacyId?: boolean;
+ includeSnoozeData?: boolean;
}): Promise> {
const { saved_object: result, ...resolveResponse } =
await this.unsecuredSavedObjectsClient.resolve('alert', id);
@@ -750,7 +752,9 @@ export class RulesClient {
result.attributes.alertTypeId,
result.attributes,
result.references,
- includeLegacyId
+ includeLegacyId,
+ false,
+ includeSnoozeData
);
return {
diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts
index 297c4b6d60fcc..b4a48be3a37fc 100644
--- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts
+++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts
@@ -201,6 +201,58 @@ describe('resolve()', () => {
`);
});
+ test('calls saved objects client with id and includeSnoozeData params', async () => {
+ const rulesClient = new RulesClient(rulesClientParams);
+ unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({
+ saved_object: {
+ id: '1',
+ type: 'alert',
+ attributes: {
+ legacyId: 'some-legacy-id',
+ alertTypeId: '123',
+ schedule: { interval: '10s' },
+ params: {
+ bar: true,
+ },
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ snoozeSchedule: [
+ {
+ duration: 10000,
+ rRule: {
+ dtstart: new Date().toISOString(),
+ tzid: 'UTC',
+ count: 1,
+ },
+ },
+ ],
+ muteAll: false,
+ actions: [
+ {
+ group: 'default',
+ actionRef: 'action_0',
+ params: {
+ foo: true,
+ },
+ },
+ ],
+ notifyWhen: 'onActiveAlert',
+ },
+ references: [
+ {
+ name: 'action_0',
+ type: 'action',
+ id: '1',
+ },
+ ],
+ },
+ outcome: 'aliasMatch',
+ alias_target_id: '2',
+ });
+ const result = await rulesClient.resolve({ id: '1', includeSnoozeData: true });
+ expect(result.isSnoozedUntil).toBeTruthy();
+ });
+
test('should call useSavedObjectReferences.injectReferences if defined for rule type', async () => {
const injectReferencesFn = jest.fn().mockReturnValue({
bar: true,
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
index d32c5bd58a94c..45147118b93aa 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
@@ -238,7 +238,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('should snooze the rule', async () => {
- const snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed');
+ let snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed');
await snoozeBadge.click();
const snoozeIndefinite = await testSubjects.find('ruleSnoozeIndefiniteApply');
@@ -247,18 +247,64 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await retry.try(async () => {
await testSubjects.existOrFail('rulesListNotifyBadge-snoozedIndefinitely');
});
+
+ // Unsnooze the rule for the next test
+ snoozeBadge = await testSubjects.find('rulesListNotifyBadge-snoozedIndefinitely');
+ await snoozeBadge.click();
+
+ const snoozeCancel = await testSubjects.find('ruleSnoozeCancel');
+ await snoozeCancel.click();
+ await pageObjects.header.waitUntilLoadingHasFinished();
});
- it('should unsnooze the rule', async () => {
- const snoozeBadge = await testSubjects.find('rulesListNotifyBadge-snoozedIndefinitely');
+ it('should snooze the rule for a set duration', async () => {
+ let snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed');
+ await snoozeBadge.click();
+
+ const snooze8h = await testSubjects.find('linkSnooze8h');
+ await snooze8h.click();
+
+ await pageObjects.header.waitUntilLoadingHasFinished();
+
+ await retry.try(async () => {
+ await testSubjects.existOrFail('rulesListNotifyBadge-snoozed');
+ });
+
+ // Unsnooze the rule for the next test
+ snoozeBadge = await testSubjects.find('rulesListNotifyBadge-snoozed');
await snoozeBadge.click();
const snoozeCancel = await testSubjects.find('ruleSnoozeCancel');
await snoozeCancel.click();
+ await pageObjects.header.waitUntilLoadingHasFinished();
+ });
+
+ it('should add snooze schedule', async () => {
+ let snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed');
+ await snoozeBadge.click();
+
+ const addScheduleButton = await testSubjects.find('ruleAddSchedule');
+ await addScheduleButton.click();
+
+ const saveScheduleButton = await testSubjects.find('scheduler-saveSchedule');
+ await saveScheduleButton.click();
+
+ await pageObjects.header.waitUntilLoadingHasFinished();
await retry.try(async () => {
- await testSubjects.existOrFail('rulesListNotifyBadge-unsnoozed');
+ await testSubjects.existOrFail('rulesListNotifyBadge-scheduled');
});
+
+ // Unsnooze the rule for the next test
+ snoozeBadge = await testSubjects.find('rulesListNotifyBadge-scheduled');
+ await snoozeBadge.click();
+
+ const snoozeCancel = await testSubjects.find('ruleRemoveAllSchedules');
+ await snoozeCancel.click();
+
+ const confirmButton = await testSubjects.find('confirmModalConfirmButton');
+ await confirmButton.click();
+ await pageObjects.header.waitUntilLoadingHasFinished();
});
});
From 36b2296fe8b5c994a320e20521e5826ec6c3804d Mon Sep 17 00:00:00 2001
From: Kurt
Date: Tue, 4 Oct 2022 12:29:11 -0400
Subject: [PATCH 38/39] Checking if security license isEnabled, and if not,
throwing 404 that is expected downstream (#142410)
---
.../share_saved_object_permissions.test.ts | 19 +++++++++++++++++++
.../spaces/share_saved_object_permissions.ts | 10 +++++++++-
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts
index e5e3135a68024..a8fa3888efeb9 100644
--- a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts
+++ b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts
@@ -36,6 +36,7 @@ describe('Share Saved Object Permissions', () => {
describe('GET /internal/security/_share_saved_object_permissions', () => {
let routeHandler: RequestHandler;
let routeConfig: RouteConfig