From 4cf6bca01e439a643d48511d6b3c82d761d8db48 Mon Sep 17 00:00:00 2001
From: Matthew Kime
Date: Tue, 15 Jun 2021 20:35:19 -0500
Subject: [PATCH 01/98] Revert "[Index Patterns] Move rollup config to index
pattern management (#102145)" (#102276)
This reverts commit f1b6fe04ed0ddbd4117bbea1c61467f0277817df.
---
.../public/constants.ts | 9 -------
.../index_pattern_management/public/mocks.ts | 9 ++++++-
.../index_pattern_management/public/plugin.ts | 5 +---
.../public/service/creation/index.ts | 2 --
.../index_pattern_management_service.ts | 27 ++++++-------------
.../public/service/list/index.ts | 2 --
x-pack/plugins/rollup/kibana.json | 1 +
.../components/rollup_prompt/index.js | 5 ++--
.../components/rollup_prompt/rollup_prompt.js | 9 +++----
.../rollup_index_pattern_creation_config.js | 21 +++++++--------
.../rollup_index_pattern_list_config.js | 7 +++--
x-pack/plugins/rollup/public/plugin.ts | 19 +++++++++++--
x-pack/plugins/rollup/tsconfig.json | 1 +
.../translations/translations/ja-JP.json | 18 ++++++-------
.../translations/translations/zh-CN.json | 18 ++++++-------
15 files changed, 73 insertions(+), 80 deletions(-)
delete mode 100644 src/plugins/index_pattern_management/public/constants.ts
rename src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts => x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js (53%)
rename src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx => x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js (76%)
rename src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js => x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js (84%)
rename src/plugins/index_pattern_management/public/service/list/rollup_list_config.js => x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js (86%)
diff --git a/src/plugins/index_pattern_management/public/constants.ts b/src/plugins/index_pattern_management/public/constants.ts
deleted file mode 100644
index e5010d133f0f3..0000000000000
--- a/src/plugins/index_pattern_management/public/constants.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns';
diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts
index 7671a532d1cb8..6c709fb14f08d 100644
--- a/src/plugins/index_pattern_management/public/mocks.ts
+++ b/src/plugins/index_pattern_management/public/mocks.ts
@@ -19,7 +19,14 @@ import {
} from './plugin';
import { IndexPatternManagmentContext } from './types';
-const createSetupContract = (): IndexPatternManagementSetup => {};
+const createSetupContract = (): IndexPatternManagementSetup => ({
+ creation: {
+ addCreationConfig: jest.fn(),
+ } as any,
+ list: {
+ addListConfig: jest.fn(),
+ } as any,
+});
const createStartContract = (): IndexPatternManagementStart => ({
creation: {
diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts
index 610b3541620b0..e3c156927bfac 100644
--- a/src/plugins/index_pattern_management/public/plugin.ts
+++ b/src/plugins/index_pattern_management/public/plugin.ts
@@ -81,10 +81,7 @@ export class IndexPatternManagementPlugin
},
});
- return this.indexPatternManagementService.setup({
- httpClient: core.http,
- uiSettings: core.uiSettings,
- });
+ return this.indexPatternManagementService.setup({ httpClient: core.http });
}
public start(core: CoreStart, plugins: IndexPatternManagementStartDependencies) {
diff --git a/src/plugins/index_pattern_management/public/service/creation/index.ts b/src/plugins/index_pattern_management/public/service/creation/index.ts
index e1f464b01e550..51610bc83e371 100644
--- a/src/plugins/index_pattern_management/public/service/creation/index.ts
+++ b/src/plugins/index_pattern_management/public/service/creation/index.ts
@@ -8,5 +8,3 @@
export { IndexPatternCreationConfig, IndexPatternCreationOption } from './config';
export { IndexPatternCreationManager } from './manager';
-// @ts-ignore
-export { RollupIndexPatternCreationConfig } from './rollup_creation_config';
diff --git a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
index 19346dbf31d18..f30ccfcb9f3ed 100644
--- a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
+++ b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
@@ -6,22 +6,11 @@
* Side Public License, v 1.
*/
-import { HttpSetup, CoreSetup } from '../../../../core/public';
-import {
- IndexPatternCreationManager,
- IndexPatternCreationConfig,
- RollupIndexPatternCreationConfig,
-} from './creation';
-import {
- IndexPatternListManager,
- IndexPatternListConfig,
- RollupIndexPatternListConfig,
-} from './list';
-
-import { CONFIG_ROLLUPS } from '../constants';
+import { HttpSetup } from '../../../../core/public';
+import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation';
+import { IndexPatternListManager, IndexPatternListConfig } from './list';
interface SetupDependencies {
httpClient: HttpSetup;
- uiSettings: CoreSetup['uiSettings'];
}
/**
@@ -38,17 +27,17 @@ export class IndexPatternManagementService {
this.indexPatternListConfig = new IndexPatternListManager();
}
- public setup({ httpClient, uiSettings }: SetupDependencies) {
+ public setup({ httpClient }: SetupDependencies) {
const creationManagerSetup = this.indexPatternCreationManager.setup(httpClient);
creationManagerSetup.addCreationConfig(IndexPatternCreationConfig);
const indexPatternListConfigSetup = this.indexPatternListConfig.setup();
indexPatternListConfigSetup.addListConfig(IndexPatternListConfig);
- if (uiSettings.get(CONFIG_ROLLUPS)) {
- creationManagerSetup.addCreationConfig(RollupIndexPatternCreationConfig);
- indexPatternListConfigSetup.addListConfig(RollupIndexPatternListConfig);
- }
+ return {
+ creation: creationManagerSetup,
+ list: indexPatternListConfigSetup,
+ };
}
public start() {
diff --git a/src/plugins/index_pattern_management/public/service/list/index.ts b/src/plugins/index_pattern_management/public/service/list/index.ts
index 738b807ac7624..620d4c7600733 100644
--- a/src/plugins/index_pattern_management/public/service/list/index.ts
+++ b/src/plugins/index_pattern_management/public/service/list/index.ts
@@ -8,5 +8,3 @@
export { IndexPatternListConfig } from './config';
export { IndexPatternListManager } from './manager';
-// @ts-ignore
-export { RollupIndexPatternListConfig } from './rollup_list_config';
diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json
index 10541d9a4ebdd..725b563c3674f 100644
--- a/x-pack/plugins/rollup/kibana.json
+++ b/x-pack/plugins/rollup/kibana.json
@@ -5,6 +5,7 @@
"server": true,
"ui": true,
"requiredPlugins": [
+ "indexPatternManagement",
"management",
"licensing",
"features"
diff --git a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js
similarity index 53%
rename from src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts
rename to x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js
index d1fc2fa242eb1..1d9eff8227c0a 100644
--- a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts
+++ b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js
@@ -1,9 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
*/
export { RollupPrompt } from './rollup_prompt';
diff --git a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js
similarity index 76%
rename from src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx
rename to x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js
index 81fcdaedb90c9..9306ab082dff4 100644
--- a/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx
+++ b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js
@@ -1,9 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
*/
import React from 'react';
@@ -15,7 +14,7 @@ export const RollupPrompt = () => (
{i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text',
+ 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text',
{
defaultMessage:
"Kibana's support for rollup index patterns is in beta. You might encounter issues using " +
@@ -26,7 +25,7 @@ export const RollupPrompt = () => (
{i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text',
+ 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text',
{
defaultMessage:
'You can match a rollup index pattern against one rollup index and zero or more regular ' +
diff --git a/src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js
similarity index 84%
rename from src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js
rename to x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js
index 2a85dfa01143c..8e5203fca9034 100644
--- a/src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js
+++ b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js
@@ -1,44 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { RollupPrompt } from './components/rollup_prompt';
-import { IndexPatternCreationConfig } from '.';
+import { IndexPatternCreationConfig } from '../../../../../src/plugins/index_pattern_management/public';
const rollupIndexPatternTypeName = i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName',
+ 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName',
{ defaultMessage: 'rollup index pattern' }
);
const rollupIndexPatternButtonText = i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText',
+ 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText',
{ defaultMessage: 'Rollup index pattern' }
);
const rollupIndexPatternButtonDescription = i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription',
+ 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription',
{ defaultMessage: 'Perform limited aggregations against summarized data' }
);
const rollupIndexPatternNoMatchError = i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError',
+ 'xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError',
{ defaultMessage: 'Rollup index pattern error: must match one rollup index' }
);
const rollupIndexPatternTooManyMatchesError = i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError',
+ 'xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError',
{ defaultMessage: 'Rollup index pattern error: can only match one rollup index' }
);
const rollupIndexPatternIndexLabel = i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel',
+ 'xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel',
{ defaultMessage: 'Rollup' }
);
@@ -128,7 +127,7 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig
if (error) {
const errorMessage = i18n.translate(
- 'indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError',
+ 'xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError',
{
defaultMessage: 'Rollup index pattern error: {error}',
values: {
diff --git a/src/plugins/index_pattern_management/public/service/list/rollup_list_config.js b/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js
similarity index 86%
rename from src/plugins/index_pattern_management/public/service/list/rollup_list_config.js
rename to x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js
index 9a80d5fd0d622..43eee6ca27f9a 100644
--- a/src/plugins/index_pattern_management/public/service/list/rollup_list_config.js
+++ b/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js
@@ -1,12 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
*/
-import { IndexPatternListConfig } from '.';
+import { IndexPatternListConfig } from '../../../../../src/plugins/index_pattern_management/public';
function isRollup(indexPattern) {
return (
diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts
index 0d345e326193c..17e352e1a4472 100644
--- a/x-pack/plugins/rollup/public/plugin.ts
+++ b/x-pack/plugins/rollup/public/plugin.ts
@@ -12,13 +12,14 @@ import { rollupBadgeExtension, rollupToggleExtension } from './extend_index_mana
import { RollupIndexPatternCreationConfig } from './index_pattern_creation/rollup_index_pattern_creation_config';
// @ts-ignore
import { RollupIndexPatternListConfig } from './index_pattern_list/rollup_index_pattern_list_config';
-import { UIM_APP_NAME } from '../common';
+import { CONFIG_ROLLUPS, UIM_APP_NAME } from '../common';
import {
FeatureCatalogueCategory,
HomePublicPluginSetup,
} from '../../../../src/plugins/home/public';
import { ManagementSetup } from '../../../../src/plugins/management/public';
import { IndexManagementPluginSetup } from '../../index_management/public';
+import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public';
// @ts-ignore
import { setHttp, init as initDocumentation } from './crud_app/services/index';
import { setNotifications, setFatalErrors, setUiStatsReporter } from './kibana_services';
@@ -28,13 +29,20 @@ export interface RollupPluginSetupDependencies {
home?: HomePublicPluginSetup;
management: ManagementSetup;
indexManagement?: IndexManagementPluginSetup;
+ indexPatternManagement: IndexPatternManagementSetup;
usageCollection?: UsageCollectionSetup;
}
export class RollupPlugin implements Plugin {
setup(
core: CoreSetup,
- { home, management, indexManagement, usageCollection }: RollupPluginSetupDependencies
+ {
+ home,
+ management,
+ indexManagement,
+ indexPatternManagement,
+ usageCollection,
+ }: RollupPluginSetupDependencies
) {
setFatalErrors(core.fatalErrors);
if (usageCollection) {
@@ -46,6 +54,13 @@ export class RollupPlugin implements Plugin {
indexManagement.extensionsService.addToggle(rollupToggleExtension);
}
+ const isRollupIndexPatternsEnabled = core.uiSettings.get(CONFIG_ROLLUPS);
+
+ if (isRollupIndexPatternsEnabled) {
+ indexPatternManagement.creation.addCreationConfig(RollupIndexPatternCreationConfig);
+ indexPatternManagement.list.addListConfig(RollupIndexPatternListConfig);
+ }
+
if (home) {
home.featureCatalogue.register({
id: 'rollup_jobs',
diff --git a/x-pack/plugins/rollup/tsconfig.json b/x-pack/plugins/rollup/tsconfig.json
index 6885081ce4bdd..9b994d1710ffc 100644
--- a/x-pack/plugins/rollup/tsconfig.json
+++ b/x-pack/plugins/rollup/tsconfig.json
@@ -16,6 +16,7 @@
"references": [
{ "path": "../../../src/core/tsconfig.json" },
// required plugins
+ { "path": "../../../src/plugins/index_pattern_management/tsconfig.json" },
{ "path": "../../../src/plugins/management/tsconfig.json" },
{ "path": "../licensing/tsconfig.json" },
{ "path": "../features/tsconfig.json" },
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 7884f27d64c0a..1f5cbcd4d6c3c 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -18015,15 +18015,15 @@
"xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel": "管理",
"xpack.rollupJobs.detailPanel.loadingLabel": "ロールアップジョブを読み込み中...",
"xpack.rollupJobs.detailPanel.notFoundLabel": "ロールアップジョブが見つかりません",
- "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription": "要約データに制限された集約を実行します。",
- "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText": "ロールアップインデックスパターン",
- "indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName": "ロールアップインデックスパターン",
- "indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel": "ロールアップ",
- "indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError": "ロールアップインデックスパターンエラー:ロールアップインデックスの 1 つと一致している必要があります",
- "indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError": "ロールアップインデックスパターンエラー:一致できるロールアップインデックスは 1 つだけです",
- "indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError": "ロールアップインデックスパターンエラー:{error}",
- "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "ロールアップインデックスパターンのKibanaのサポートはベータ版です。保存された検索、可視化、ダッシュボードでこれらのパターンを使用すると問題が発生する場合があります。Timelionや機械学習などの一部の高度な機能ではサポートされていません。",
- "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "ロールアップインデックスパターンは、1つのロールアップインデックスとゼロ以上の標準インデックスと一致させることができます。ロールアップインデックスパターンでは、メトリック、フィールド、間隔、アグリゲーションが制限されています。ロールアップインデックスは、1つのジョブ構成があるインデックス、または複数のジョブと互換する構成があるインデックスに制限されています。",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription": "要約データに制限された集約を実行します。",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText": "ロールアップインデックスパターン",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName": "ロールアップインデックスパターン",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel": "ロールアップ",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError": "ロールアップインデックスパターンエラー:ロールアップインデックスの 1 つと一致している必要があります",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError": "ロールアップインデックスパターンエラー:一致できるロールアップインデックスは 1 つだけです",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError": "ロールアップインデックスパターンエラー:{error}",
+ "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "ロールアップインデックスパターンのKibanaのサポートはベータ版です。保存された検索、可視化、ダッシュボードでこれらのパターンを使用すると問題が発生する場合があります。Timelionや機械学習などの一部の高度な機能ではサポートされていません。",
+ "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "ロールアップインデックスパターンは、1つのロールアップインデックスとゼロ以上の標準インデックスと一致させることができます。ロールアップインデックスパターンでは、メトリック、フィールド、間隔、アグリゲーションが制限されています。ロールアップインデックスは、1つのジョブ構成があるインデックス、または複数のジョブと互換する構成があるインデックスに制限されています。",
"xpack.rollupJobs.featureCatalogueDescription": "今後の分析用に履歴データを小さなインデックスに要約して格納します。",
"xpack.rollupJobs.indexMgmtBadge.rollupLabel": "ロールアップ",
"xpack.rollupJobs.indexMgmtToggle.toggleLabel": "ロールアップインデックスを含める",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 9685a5015710e..8006153cafb06 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -18255,15 +18255,15 @@
"xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel": "管理",
"xpack.rollupJobs.detailPanel.loadingLabel": "正在加载汇总/打包作业……",
"xpack.rollupJobs.detailPanel.notFoundLabel": "未找到汇总/打包作业",
- "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription": "针对汇总数据执行有限聚合",
- "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText": "汇总/打包索引模式",
- "indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName": "汇总/打包索引模式",
- "indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel": "汇总/打包",
- "indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError": "汇总/打包索引模式错误:必须匹配一个汇总/打包索引",
- "indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError": "汇总/打包索引模式错误:只能匹配一个汇总/打包索引",
- "indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError": "汇总索引模式错误:{error}",
- "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "Kibana 对汇总/打包索引模式的支持处于公测版状态。将这些模式用于已保存搜索、可视化以及仪表板可能会遇到问题。某些高级功能,如 Timelion 和 Machine Learning,不支持这些模式。",
- "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "可以根据一个汇总/打包索引和零个或更多常规索引匹配汇总/打包索引模式。汇总/打包索引模式的指标、字段、时间间隔和聚合有限。汇总/打包索引仅限于具有一个作业配置或多个作业配置兼容的索引。",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription": "针对汇总数据执行有限聚合",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText": "汇总/打包索引模式",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName": "汇总/打包索引模式",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel": "汇总/打包",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError": "汇总/打包索引模式错误:必须匹配一个汇总/打包索引",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError": "汇总/打包索引模式错误:只能匹配一个汇总/打包索引",
+ "xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError": "汇总索引模式错误:{error}",
+ "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "Kibana 对汇总/打包索引模式的支持处于公测版状态。将这些模式用于已保存搜索、可视化以及仪表板可能会遇到问题。某些高级功能,如 Timelion 和 Machine Learning,不支持这些模式。",
+ "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "可以根据一个汇总/打包索引和零个或更多常规索引匹配汇总/打包索引模式。汇总/打包索引模式的指标、字段、时间间隔和聚合有限。汇总/打包索引仅限于具有一个作业配置或多个作业配置兼容的索引。",
"xpack.rollupJobs.featureCatalogueDescription": "汇总历史数据并将其存储在较小的索引中以供将来分析。",
"xpack.rollupJobs.indexMgmtBadge.rollupLabel": "汇总/打包",
"xpack.rollupJobs.indexMgmtToggle.toggleLabel": "包括汇总索引",
From 93d63b9b7de07e041b3d5d0b9986aa1eec44bbfb Mon Sep 17 00:00:00 2001
From: Giovanni Geraci
Date: Wed, 16 Jun 2021 03:43:34 +0200
Subject: [PATCH 02/98] Remove old limit on Console application (#100882)
In latest stable version of Kibana(7.13), Console application seems to be not limited anymore to connecting only to the first host defined in elasticsearch.hosts defined on kibana.yml.
On previous Kibana releases, if the first Elasticsearch host was not available(i.e. maintenance or failure), Console application was returning an error: "Client request error: connect EHOSTUNREACH".
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
docs/user/production-considerations/production.asciidoc | 2 --
1 file changed, 2 deletions(-)
diff --git a/docs/user/production-considerations/production.asciidoc b/docs/user/production-considerations/production.asciidoc
index 1ffca4b6ae6ab..b75b556588cfd 100644
--- a/docs/user/production-considerations/production.asciidoc
+++ b/docs/user/production-considerations/production.asciidoc
@@ -122,8 +122,6 @@ active in case of failure from the currently used instance.
Kibana can be configured to connect to multiple Elasticsearch nodes in the same cluster. In situations where a node becomes unavailable,
Kibana will transparently connect to an available node and continue operating. Requests to available hosts will be routed in a round robin fashion.
-Currently the Console application is limited to connecting to the first node listed.
-
In kibana.yml:
[source,js]
--------
From 752609dc477c1e82817c0093c255bd14745a24b6 Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Tue, 15 Jun 2021 21:34:01 -0700
Subject: [PATCH 03/98] skip flaky suite (#102282)
---
x-pack/test/api_integration/apis/ml/modules/index.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/api_integration/apis/ml/modules/index.ts b/x-pack/test/api_integration/apis/ml/modules/index.ts
index ab46c4f0333c8..dae0044c47cca 100644
--- a/x-pack/test/api_integration/apis/ml/modules/index.ts
+++ b/x-pack/test/api_integration/apis/ml/modules/index.ts
@@ -12,7 +12,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
const fleetPackages = ['apache-0.5.0', 'nginx-0.5.0'];
- describe('modules', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/102282
+ describe.skip('modules', function () {
before(async () => {
for (const fleetPackage of fleetPackages) {
await ml.testResources.installFleetPackage(fleetPackage);
From 4cd073f65109f03b29dc5179dec120f5e141c07a Mon Sep 17 00:00:00 2001
From: Pete Hampton
Date: Wed, 16 Jun 2021 08:11:32 +0100
Subject: [PATCH 04/98] Bug: ES query returning no records for d-rule alerts.
(#102160)
---
.../server/usage/detections/detection_rule_helpers.ts | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts
index ebcda69441135..8d5a2efc7fae1 100644
--- a/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts
+++ b/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts
@@ -177,6 +177,8 @@ export const updateDetectionRuleUsage = (
return updatedUsage;
};
+const MAX_RESULTS_WINDOW = 10_000; // elasticsearch index.max_result_window default value
+
export const getDetectionRuleMetrics = async (
kibanaIndex: string,
signalsIndex: string,
@@ -189,14 +191,14 @@ export const getDetectionRuleMetrics = async (
filterPath: [],
ignoreUnavailable: true,
index: kibanaIndex,
- size: 10_000, // elasticsearch index.max_result_window default value
+ size: MAX_RESULTS_WINDOW,
};
try {
const { body: ruleResults } = await esClient.search(ruleSearchOptions);
const { body: detectionAlertsResp } = (await esClient.search({
index: `${signalsIndex}*`,
- size: 0,
+ size: MAX_RESULTS_WINDOW,
body: {
aggs: {
detectionAlerts: {
@@ -224,7 +226,7 @@ export const getDetectionRuleMetrics = async (
type: 'cases-comments',
fields: [],
page: 1,
- perPage: 10_000,
+ perPage: MAX_RESULTS_WINDOW,
filter: 'cases-comments.attributes.type: alert',
});
From f4d22b36fb300e8cbcee1606d1f7fde70b58b9b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?=
Date: Wed, 16 Jun 2021 09:13:37 +0200
Subject: [PATCH 05/98] [ML] Adds popover help for ROC curve (#101893)
Co-authored-by: Lisa Cawley
---
.../evaluate_panel.tsx | 15 +----
.../roc_curve_help_popover.tsx | 55 +++++++++++++++++++
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
4 files changed, 58 insertions(+), 14 deletions(-)
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/roc_curve_help_popover.tsx
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx
index bc1c9dbed1dcc..086adcecd077a 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx
@@ -17,7 +17,6 @@ import {
EuiDataGridPopoverContents,
EuiFlexGroup,
EuiFlexItem,
- EuiIconTip,
EuiSpacer,
EuiText,
EuiTitle,
@@ -51,6 +50,7 @@ import { isTrainingFilter } from './is_training_filter';
import { useRocCurve } from './use_roc_curve';
import { useConfusionMatrix } from './use_confusion_matrix';
import { MulticlassConfusionMatrixHelpPopover } from './confusion_matrix_help_popover';
+import { RocCurveHelpPopover } from './roc_curve_help_popover';
export interface EvaluatePanelProps {
jobConfig: DataFrameAnalyticsConfig;
@@ -409,7 +409,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, se
{/* AUC ROC Chart */}
-
+
= ({ jobConfig, jobStatus, se
-
+
{Array.isArray(errorRocCurve) && (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/roc_curve_help_popover.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/roc_curve_help_popover.tsx
new file mode 100644
index 0000000000000..f828cbabde894
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/roc_curve_help_popover.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ HelpPopover,
+ HelpPopoverButton,
+} from '../../../../../components/help_popover/help_popover';
+
+export const RocCurveHelpPopover = () => {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+ return (
+ {
+ setIsPopoverOpen(!isPopoverOpen);
+ }}
+ />
+ }
+ closePopover={() => setIsPopoverOpen(false)}
+ isOpen={isPopoverOpen}
+ title={i18n.translate('xpack.ml.dataframe.analytics.rocCurvePopoverTitle', {
+ defaultMessage: 'Receiver operating characteristic (ROC) curve',
+ })}
+ >
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 1f5cbcd4d6c3c..c67dd383a2ea2 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -14027,7 +14027,6 @@
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionMeanRecallStat": "平均再現率",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionOverallAccuracyStat": "全体的な精度",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionOverallAccuracyTooltip": "合計予測数に対する正しいクラス予測数の比率。",
- "xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionRocInfoTooltip": "受信者操作特性 (ROC) 曲線は、異なる予測確率しきい値で分類プロセスのパフォーマンスを表すプロットです。",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionRocTitle": "受信者操作特性 (ROC) 曲線",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionTitle": "モデル評価",
"xpack.ml.dataframe.analytics.classificationExploration.showActions": "アクションを表示",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 8006153cafb06..23cde5dd1fcff 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -14208,7 +14208,6 @@
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionMeanRecallStat": "平均召回率",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionOverallAccuracyStat": "总体准确率",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionOverallAccuracyTooltip": "正确类预测数目与预测总数的比率。",
- "xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionRocInfoTooltip": "接受者操作特性 (ROC) 曲线是表示在不同预测概率阈值下分类过程的性能绘图。",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionRocTitle": "接受者操作特性 (ROC) 曲线",
"xpack.ml.dataframe.analytics.classificationExploration.evaluateSectionTitle": "模型评估",
"xpack.ml.dataframe.analytics.classificationExploration.generalizationDocsCount": "{docsCount, plural, other {个文档}}已评估",
From f39bf9c985b139e29e9ac8d8c0b37ab1f96edbf7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Wed, 16 Jun 2021 09:35:08 +0200
Subject: [PATCH 06/98] [APM] More styles fixes related to the new page
template (#102253)
* [APM] Service map: Add border to panel
* [APM] Metrics: Add border to panel
* [APM] Service overview: Add border to panel
* [APM] Transactions and charts: Add border to panel
---
.../apm/public/components/app/service_map/index.tsx | 2 +-
.../apm/public/components/app/service_metrics/index.tsx | 2 +-
.../public/components/app/service_node_metrics/index.tsx | 6 ++++--
.../apm/public/components/app/service_overview/index.tsx | 8 ++++----
.../service_overview_instances_chart_and_table.tsx | 2 +-
.../service_overview_throughput_chart.tsx | 2 +-
.../transaction_details/WaterfallWithSummmary/index.tsx | 4 ++--
.../public/components/app/transaction_details/index.tsx | 6 ++++--
.../public/components/app/transaction_overview/index.tsx | 2 +-
.../charts/instances_latency_distribution_chart/index.tsx | 2 +-
.../shared/charts/transaction_breakdown_chart/index.tsx | 2 +-
.../components/shared/charts/transaction_charts/index.tsx | 4 ++--
.../shared/charts/transaction_error_rate_chart/index.tsx | 2 +-
13 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx
index df8438c5c80a4..582eafe7553af 100644
--- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx
@@ -146,7 +146,7 @@ export function ServiceMap({
return (
<>
-
+
{data.charts.map((chart) => (
-
+
) : (
-
+
+
+
)}
@@ -171,7 +173,7 @@ export function ServiceNodeMetrics({
{data.charts.map((chart) => (
-
+
-
+
@@ -63,7 +63,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) {
-
+
@@ -84,7 +84,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) {
)}
-
+
@@ -101,7 +101,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) {
{!isRumAgent && (
-
+
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
index 8513e0835d373..719409b0f97ff 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
@@ -228,7 +228,7 @@ export function ServiceOverviewInstancesChartAndTable({
/>
-
+
+
{i18n.translate('xpack.apm.serviceOverview.throughtputChartTitle', {
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/index.tsx
index 3d3ce3262f13b..6f5b95b103f6b 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/index.tsx
@@ -83,13 +83,13 @@ export function WaterfallWithSummmary({
/>
);
- return {content} ;
+ return {content} ;
}
const entryTransaction = entryWaterfallTransaction.doc;
return (
-
+
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
index b9508b2d303a2..3cac05ba2d96a 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx
@@ -76,11 +76,13 @@ export function TransactionDetails() {
return (
<>
+
+
{transactionName}
-
+
@@ -88,7 +90,7 @@ export function TransactionDetails() {
-
+
-
+
Transactions
diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
index ce4f36ced7903..0ad4be17e35cb 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
@@ -104,7 +104,7 @@ export function InstancesLatencyDistributionChart({
};
return (
-
+
{i18n.translate('xpack.apm.instancesLatencyDistributionChartTitle', {
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx
index 978604c4c96ec..40c5e39589fb1 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx
@@ -22,7 +22,7 @@ export function TransactionBreakdownChart({
const { timeseries } = data;
return (
-
+
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx
index 3f868ae272e3a..019a25b1e9ed3 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx
@@ -37,13 +37,13 @@ export function TransactionCharts() {
-
+
-
+
{i18n.translate(
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx
index 7eceaf5ca8e5d..96cb7c49a6710 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx
@@ -135,7 +135,7 @@ export function TransactionErrorRateChart({
];
return (
-
+
{i18n.translate('xpack.apm.errorRate', {
From c9678f29e2df52ac19eb45067d6c072865389660 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Wed, 16 Jun 2021 09:43:35 +0200
Subject: [PATCH 07/98] [UX] Update panels to use border not shadow (#102262)
---
.../components/app/RumDashboard/ClientMetrics/index.tsx | 2 +-
.../components/app/RumDashboard/ImpactfulMetrics/index.tsx | 2 +-
.../components/app/RumDashboard/Panels/PageLoadAndViews.tsx | 4 ++--
.../components/app/RumDashboard/Panels/VisitorBreakdowns.tsx | 4 ++--
.../public/components/app/RumDashboard/UXMetrics/index.tsx | 2 +-
5 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
index add6ac1b08b28..c525a71ea4589 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx
@@ -24,7 +24,7 @@ export function ClientMetrics() {
} = useUrlParams();
return (
-
+
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/index.tsx
index 175b40f85d64b..b696a46f59bd1 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/index.tsx
@@ -11,7 +11,7 @@ import { JSErrors } from './JSErrors';
export function ImpactfulMetrics() {
return (
-
+
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/PageLoadAndViews.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/PageLoadAndViews.tsx
index b51e2559b7f15..9dd83fd1c8fd1 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/PageLoadAndViews.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/PageLoadAndViews.tsx
@@ -14,12 +14,12 @@ export function PageLoadAndViews() {
return (
-
+
-
+
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/VisitorBreakdowns.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/VisitorBreakdowns.tsx
index 0433988ecfa21..ff79feaa924f3 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/VisitorBreakdowns.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/VisitorBreakdowns.tsx
@@ -14,12 +14,12 @@ export function VisitorBreakdownsPanel() {
return (
-
+
-
+
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx
index 44212fed98987..a665b6560c7e9 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx
@@ -62,7 +62,7 @@ export function UXMetrics() {
);
return (
-
+
From b96914235e8112cc3cc4f3d7a197560f684a2d36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Wed, 16 Jun 2021 09:43:58 +0200
Subject: [PATCH 08/98] [Observability] Add border to section container panels
(#102259)
---
.../observability/public/components/app/section/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx
index 191a1b2890ada..cbe0c45bbd169 100644
--- a/x-pack/plugins/observability/public/components/app/section/index.tsx
+++ b/x-pack/plugins/observability/public/components/app/section/index.tsx
@@ -25,7 +25,7 @@ interface Props {
export function SectionContainer({ title, appLink, children, hasError }: Props) {
const { core } = usePluginContext();
return (
-
+
Date: Wed, 16 Jun 2021 10:00:05 +0200
Subject: [PATCH 09/98] [Security Solution][Timeline] Fix User not able to
scroll down and access Alert table on adding long content in Timeline's
Description (#101486)
* Add LineClamp component to timeline description
* Truncate timeline description on timeline table
* Fix StyledLineClamp styled component performance issue
Read more: https://styled-components.com/docs/faqs#why-should-i-avoid-declaring-styled-components-in-the-render-method
---
.../common/components/line_clamp/index.tsx | 29 ++++++++++++-------
.../components/flyout/header/index.tsx | 11 +++----
.../timelines_table/common_columns.tsx | 14 +++++++--
3 files changed, 35 insertions(+), 19 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx
index 896b0ec5fd8df..d8895490d1e0f 100644
--- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx
@@ -13,15 +13,6 @@ import * as i18n from './translations';
const LINE_CLAMP = 3;
const LINE_CLAMP_HEIGHT = 5.5;
-const StyledLineClamp = styled.div`
- display: -webkit-box;
- -webkit-line-clamp: ${LINE_CLAMP};
- -webkit-box-orient: vertical;
- overflow: hidden;
- max-height: ${`${LINE_CLAMP_HEIGHT}em`};
- height: ${`${LINE_CLAMP_HEIGHT}em`};
-`;
-
const ReadMore = styled(EuiButtonEmpty)`
span.euiButtonContent {
padding: 0;
@@ -35,7 +26,19 @@ const ExpandedContent = styled.div`
overflow-y: auto;
`;
-const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) => {
+const StyledLineClamp = styled.div<{ lineClampHeight: number }>`
+ display: -webkit-box;
+ -webkit-line-clamp: ${LINE_CLAMP};
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ max-height: ${({ lineClampHeight }) => lineClampHeight}em;
+ height: ${({ lineClampHeight }) => lineClampHeight}em;
+`;
+
+const LineClampComponent: React.FC<{
+ content?: string | null;
+ lineClampHeight?: number;
+}> = ({ content, lineClampHeight = LINE_CLAMP_HEIGHT }) => {
const [isOverflow, setIsOverflow] = useState(null);
const [isExpanded, setIsExpanded] = useState(null);
const descriptionRef = useRef(null);
@@ -71,7 +74,11 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content })
{content}
) : isOverflow == null || isOverflow === true ? (
-
+
{content}
) : (
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
index da45579b34773..dd8cdb818cad7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
@@ -52,6 +52,7 @@ import * as i18n from './translations';
import * as commonI18n from '../../timeline/properties/translations';
import { getTimelineStatusByIdSelector } from './selectors';
import { TimelineKPIs } from './kpis';
+import { LineClamp } from '../../../../common/components/line_clamp';
// to hide side borders
const StyledPanel = styled(EuiPanel)`
@@ -206,13 +207,13 @@ const TimelineDescriptionComponent: React.FC = ({ timelineId
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).description
);
- const content = useMemo(() => (description.length ? description : commonI18n.DESCRIPTION), [
- description,
- ]);
-
return (
- {content}
+ {description.length ? (
+
+ ) : (
+ commonI18n.DESCRIPTION
+ )}
);
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx
index 98d678a25b4c6..65963c9609320 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx
@@ -10,7 +10,7 @@
import { EuiButtonIcon, EuiLink } from '@elastic/eui';
import { omit } from 'lodash/fp';
import React from 'react';
-
+import styled from 'styled-components';
import { ACTION_COLUMN_WIDTH } from './common_styles';
import { isUntitled } from '../helpers';
import { NotePreviews } from '../note_previews';
@@ -20,6 +20,14 @@ import { getEmptyTagValue } from '../../../../common/components/empty_value';
import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date';
import { TimelineType } from '../../../../../common/types/timeline';
+const DescriptionCell = styled.span`
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 5;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+`;
+
/**
* Returns the column definitions (passed as the `columns` prop to
* `EuiBasicTable`) that are common to the compact `Open Timeline` modal view,
@@ -85,9 +93,9 @@ export const getCommonColumns = ({
field: 'description',
name: i18n.DESCRIPTION,
render: (description: string) => (
-
+
{description != null && description.trim().length > 0 ? description : getEmptyTagValue()}
-
+
),
sortable: false,
},
From 4180a026b736058727466915801c109aa58fdd10 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Wed, 16 Jun 2021 10:22:26 +0200
Subject: [PATCH 10/98] [Lens] Formula overall functions (#99461)
---
...ns-public.expressionfunctiondefinitions.md | 1 +
...ssionfunctiondefinitions.overall_metric.md | 11 +
...ns-server.expressionfunctiondefinitions.md | 1 +
...ssionfunctiondefinitions.overall_metric.md | 11 +
.../expression_functions/specs/index.ts | 1 +
.../specs/overall_metric.ts | 168 +++++++
.../specs/tests/overall_metric.test.ts | 450 ++++++++++++++++++
.../common/expression_functions/types.ts | 2 +
.../common/service/expressions_services.ts | 2 +
src/plugins/expressions/public/public.api.md | 4 +
src/plugins/expressions/server/server.api.md | 4 +
.../dimension_panel/dimension_editor.tsx | 5 +
.../definitions/calculations/index.ts | 10 +
.../calculations/overall_metric.tsx | 224 +++++++++
.../definitions/calculations/utils.ts | 32 ++
.../operations/definitions/index.ts | 24 +
.../operations/index.ts | 4 +
.../operations/operations.test.ts | 16 +
18 files changed, 970 insertions(+)
create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md
create mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md
create mode 100644 src/plugins/expressions/common/expression_functions/specs/overall_metric.ts
create mode 100644 src/plugins/expressions/common/expression_functions/specs/tests/overall_metric.test.ts
create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md
index c6e00842a31e6..2c03db82ba683 100644
--- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md
@@ -21,6 +21,7 @@ export interface ExpressionFunctionDefinitions
| [derivative](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative
| |
| [font](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont
| |
| [moving\_average](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md) | ExpressionFunctionMovingAverage
| |
+| [overall\_metric](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md) | ExpressionFunctionOverallMetric
| |
| [theme](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.theme.md) | ExpressionFunctionTheme
| |
| [var\_set](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.var_set.md) | ExpressionFunctionVarSet
| |
| [var](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.var.md) | ExpressionFunctionVar
| |
diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md
new file mode 100644
index 0000000000000..8685788a2f351
--- /dev/null
+++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md) > [overall\_metric](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md)
+
+## ExpressionFunctionDefinitions.overall\_metric property
+
+Signature:
+
+```typescript
+overall_metric: ExpressionFunctionOverallMetric;
+```
diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md
index 219678244951b..f55fed99e1d3d 100644
--- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md
+++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md
@@ -21,6 +21,7 @@ export interface ExpressionFunctionDefinitions
| [derivative](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative
| |
| [font](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont
| |
| [moving\_average](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md) | ExpressionFunctionMovingAverage
| |
+| [overall\_metric](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md) | ExpressionFunctionOverallMetric
| |
| [theme](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.theme.md) | ExpressionFunctionTheme
| |
| [var\_set](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.var_set.md) | ExpressionFunctionVarSet
| |
| [var](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.var.md) | ExpressionFunctionVar
| |
diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md
new file mode 100644
index 0000000000000..b8564a696e6e4
--- /dev/null
+++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md) > [overall\_metric](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md)
+
+## ExpressionFunctionDefinitions.overall\_metric property
+
+Signature:
+
+```typescript
+overall_metric: ExpressionFunctionOverallMetric;
+```
diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts
index 20a6f9aac4567..c6d89f41d0e0d 100644
--- a/src/plugins/expressions/common/expression_functions/specs/index.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/index.ts
@@ -12,6 +12,7 @@ export * from './var_set';
export * from './var';
export * from './theme';
export * from './cumulative_sum';
+export * from './overall_metric';
export * from './derivative';
export * from './moving_average';
export * from './ui_setting';
diff --git a/src/plugins/expressions/common/expression_functions/specs/overall_metric.ts b/src/plugins/expressions/common/expression_functions/specs/overall_metric.ts
new file mode 100644
index 0000000000000..e42112d3a23ed
--- /dev/null
+++ b/src/plugins/expressions/common/expression_functions/specs/overall_metric.ts
@@ -0,0 +1,168 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { ExpressionFunctionDefinition } from '../types';
+import { Datatable } from '../../expression_types';
+import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers';
+
+export interface OverallMetricArgs {
+ by?: string[];
+ inputColumnId: string;
+ outputColumnId: string;
+ outputColumnName?: string;
+ metric: 'sum' | 'min' | 'max' | 'average';
+}
+
+export type ExpressionFunctionOverallMetric = ExpressionFunctionDefinition<
+ 'overall_metric',
+ Datatable,
+ OverallMetricArgs,
+ Datatable
+>;
+
+function getValueAsNumberArray(value: unknown) {
+ if (Array.isArray(value)) {
+ return value.map((innerVal) => Number(innerVal));
+ } else {
+ return [Number(value)];
+ }
+}
+
+/**
+ * Calculates the overall metric of a specified column in the data table.
+ *
+ * Also supports multiple series in a single data table - use the `by` argument
+ * to specify the columns to split the calculation by.
+ * For each unique combination of all `by` columns a separate overall metric will be calculated.
+ * The order of rows won't be changed - this function is not modifying any existing columns, it's only
+ * adding the specified `outputColumnId` column to every row of the table without adding or removing rows.
+ *
+ * Behavior:
+ * * Will write the overall metric of `inputColumnId` into `outputColumnId`
+ * * If provided will use `outputColumnName` as name for the newly created column. Otherwise falls back to `outputColumnId`
+ * * Each cell will contain the calculated metric based on the values of all cells belonging to the current series.
+ *
+ * Edge cases:
+ * * Will return the input table if `inputColumnId` does not exist
+ * * Will throw an error if `outputColumnId` exists already in provided data table
+ * * If the row value contains `null` or `undefined`, it will be ignored and overwritten with the overall metric of
+ * all cells of the same series.
+ * * For all values besides `null` and `undefined`, the value will be cast to a number before it's added to the
+ * overall metric of the current series - if this results in `NaN` (like in case of objects), all cells of the
+ * current series will be set to `NaN`.
+ * * To determine separate series defined by the `by` columns, the values of these columns will be cast to strings
+ * before comparison. If the values are objects, the return value of their `toString` method will be used for comparison.
+ * Missing values (`null` and `undefined`) will be treated as empty strings.
+ */
+export const overallMetric: ExpressionFunctionOverallMetric = {
+ name: 'overall_metric',
+ type: 'datatable',
+
+ inputTypes: ['datatable'],
+
+ help: i18n.translate('expressions.functions.overallMetric.help', {
+ defaultMessage: 'Calculates the overall sum, min, max or average of a column in a data table',
+ }),
+
+ args: {
+ by: {
+ help: i18n.translate('expressions.functions.overallMetric.args.byHelpText', {
+ defaultMessage: 'Column to split the overall calculation by',
+ }),
+ multi: true,
+ types: ['string'],
+ required: false,
+ },
+ metric: {
+ help: i18n.translate('expressions.functions.overallMetric.metricHelpText', {
+ defaultMessage: 'Metric to calculate',
+ }),
+ types: ['string'],
+ options: ['sum', 'min', 'max', 'average'],
+ },
+ inputColumnId: {
+ help: i18n.translate('expressions.functions.overallMetric.args.inputColumnIdHelpText', {
+ defaultMessage: 'Column to calculate the overall metric of',
+ }),
+ types: ['string'],
+ required: true,
+ },
+ outputColumnId: {
+ help: i18n.translate('expressions.functions.overallMetric.args.outputColumnIdHelpText', {
+ defaultMessage: 'Column to store the resulting overall metric in',
+ }),
+ types: ['string'],
+ required: true,
+ },
+ outputColumnName: {
+ help: i18n.translate('expressions.functions.overallMetric.args.outputColumnNameHelpText', {
+ defaultMessage: 'Name of the column to store the resulting overall metric in',
+ }),
+ types: ['string'],
+ required: false,
+ },
+ },
+
+ fn(input, { by, inputColumnId, outputColumnId, outputColumnName, metric }) {
+ const resultColumns = buildResultColumns(
+ input,
+ outputColumnId,
+ inputColumnId,
+ outputColumnName
+ );
+
+ if (!resultColumns) {
+ return input;
+ }
+
+ const accumulators: Partial> = {};
+ const valueCounter: Partial> = {};
+ input.rows.forEach((row) => {
+ const bucketIdentifier = getBucketIdentifier(row, by);
+ const accumulatorValue = accumulators[bucketIdentifier] ?? 0;
+
+ const currentValue = row[inputColumnId];
+ if (currentValue != null) {
+ const currentNumberValues = getValueAsNumberArray(currentValue);
+ switch (metric) {
+ case 'average':
+ valueCounter[bucketIdentifier] =
+ (valueCounter[bucketIdentifier] ?? 0) + currentNumberValues.length;
+ case 'sum':
+ accumulators[bucketIdentifier] =
+ accumulatorValue + currentNumberValues.reduce((a, b) => a + b, 0);
+ break;
+ case 'min':
+ accumulators[bucketIdentifier] = Math.min(accumulatorValue, ...currentNumberValues);
+ break;
+ case 'max':
+ accumulators[bucketIdentifier] = Math.max(accumulatorValue, ...currentNumberValues);
+ break;
+ }
+ }
+ });
+ if (metric === 'average') {
+ Object.keys(accumulators).forEach((bucketIdentifier) => {
+ accumulators[bucketIdentifier] =
+ accumulators[bucketIdentifier]! / valueCounter[bucketIdentifier]!;
+ });
+ }
+ return {
+ ...input,
+ columns: resultColumns,
+ rows: input.rows.map((row) => {
+ const newRow = { ...row };
+ const bucketIdentifier = getBucketIdentifier(row, by);
+ newRow[outputColumnId] = accumulators[bucketIdentifier];
+
+ return newRow;
+ }),
+ };
+ },
+};
diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/overall_metric.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/overall_metric.test.ts
new file mode 100644
index 0000000000000..30354c4e54dc7
--- /dev/null
+++ b/src/plugins/expressions/common/expression_functions/specs/tests/overall_metric.test.ts
@@ -0,0 +1,450 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { functionWrapper } from './utils';
+import { ExecutionContext } from '../../../execution/types';
+import { Datatable } from '../../../expression_types/specs/datatable';
+import { overallMetric, OverallMetricArgs } from '../overall_metric';
+
+describe('interpreter/functions#overall_metric', () => {
+ const fn = functionWrapper(overallMetric);
+ const runFn = (input: Datatable, args: OverallMetricArgs) =>
+ fn(input, args, {} as ExecutionContext) as Datatable;
+
+ it('calculates overall sum', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
+ rows: [{ val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'sum' }
+ );
+ expect(result.columns).toContainEqual({
+ id: 'output',
+ name: 'output',
+ meta: { type: 'number' },
+ });
+ expect(result.rows.map((row) => row.output)).toEqual([17, 17, 17, 17]);
+ });
+
+ it('ignores null or undefined', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
+ rows: [{}, { val: null }, { val: undefined }, { val: 1 }, { val: 5 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'average' }
+ );
+ expect(result.columns).toContainEqual({
+ id: 'output',
+ name: 'output',
+ meta: { type: 'number' },
+ });
+ expect(result.rows.map((row) => row.output)).toEqual([3, 3, 3, 3, 3]);
+ });
+
+ it('calculates overall sum for multiple series', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [
+ { id: 'val', name: 'val', meta: { type: 'number' } },
+ { id: 'split', name: 'split', meta: { type: 'string' } },
+ ],
+ rows: [
+ { val: 1, split: 'A' },
+ { val: 2, split: 'B' },
+ { val: 3, split: 'B' },
+ { val: 4, split: 'A' },
+ { val: 5, split: 'A' },
+ { val: 6, split: 'A' },
+ { val: 7, split: 'B' },
+ { val: 8, split: 'B' },
+ ],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', by: ['split'], metric: 'sum' }
+ );
+
+ expect(result.rows.map((row) => row.output)).toEqual([
+ 1 + 4 + 5 + 6,
+ 2 + 3 + 7 + 8,
+ 2 + 3 + 7 + 8,
+ 1 + 4 + 5 + 6,
+ 1 + 4 + 5 + 6,
+ 1 + 4 + 5 + 6,
+ 2 + 3 + 7 + 8,
+ 2 + 3 + 7 + 8,
+ ]);
+ });
+
+ it('treats missing split column as separate series', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [
+ { id: 'val', name: 'val', meta: { type: 'number' } },
+ { id: 'split', name: 'split', meta: { type: 'string' } },
+ ],
+ rows: [
+ { val: 1, split: 'A' },
+ { val: 2, split: 'B' },
+ { val: 3 },
+ { val: 4, split: 'A' },
+ { val: 5 },
+ { val: 6, split: 'A' },
+ { val: 7, split: 'B' },
+ { val: 8, split: 'B' },
+ ],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', by: ['split'], metric: 'sum' }
+ );
+ expect(result.rows.map((row) => row.output)).toEqual([
+ 1 + 4 + 6,
+ 2 + 7 + 8,
+ 3 + 5,
+ 1 + 4 + 6,
+ 3 + 5,
+ 1 + 4 + 6,
+ 2 + 7 + 8,
+ 2 + 7 + 8,
+ ]);
+ });
+
+ it('treats null like undefined and empty string for split columns', () => {
+ const table: Datatable = {
+ type: 'datatable',
+ columns: [
+ { id: 'val', name: 'val', meta: { type: 'number' } },
+ { id: 'split', name: 'split', meta: { type: 'string' } },
+ ],
+ rows: [
+ { val: 1, split: 'A' },
+ { val: 2, split: 'B' },
+ { val: 3 },
+ { val: 4, split: 'A' },
+ { val: 5 },
+ { val: 6, split: 'A' },
+ { val: 7, split: null },
+ { val: 8, split: 'B' },
+ { val: 9, split: '' },
+ ],
+ };
+
+ const result = runFn(table, {
+ inputColumnId: 'val',
+ outputColumnId: 'output',
+ by: ['split'],
+ metric: 'sum',
+ });
+ expect(result.rows.map((row) => row.output)).toEqual([
+ 1 + 4 + 6,
+ 2 + 8,
+ 3 + 5 + 7 + 9,
+ 1 + 4 + 6,
+ 3 + 5 + 7 + 9,
+ 1 + 4 + 6,
+ 3 + 5 + 7 + 9,
+ 2 + 8,
+ 3 + 5 + 7 + 9,
+ ]);
+
+ const result2 = runFn(table, {
+ inputColumnId: 'val',
+ outputColumnId: 'output',
+ by: ['split'],
+ metric: 'max',
+ });
+ expect(result2.rows.map((row) => row.output)).toEqual([6, 8, 9, 6, 9, 6, 9, 8, 9]);
+ });
+
+ it('handles array values', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
+ rows: [{ val: 5 }, { val: [7, 10] }, { val: [3, 1] }, { val: 2 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'sum' }
+ );
+ expect(result.columns).toContainEqual({
+ id: 'output',
+ name: 'output',
+ meta: { type: 'number' },
+ });
+ expect(result.rows.map((row) => row.output)).toEqual([28, 28, 28, 28]);
+ });
+
+ it('takes array values into account for average calculation', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
+ rows: [{ val: [3, 4] }, { val: 2 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'average' }
+ );
+ expect(result.columns).toContainEqual({
+ id: 'output',
+ name: 'output',
+ meta: { type: 'number' },
+ });
+ expect(result.rows.map((row) => row.output)).toEqual([3, 3]);
+ });
+
+ it('handles array values for split columns', () => {
+ const table: Datatable = {
+ type: 'datatable',
+ columns: [
+ { id: 'val', name: 'val', meta: { type: 'number' } },
+ { id: 'split', name: 'split', meta: { type: 'string' } },
+ ],
+ rows: [
+ { val: 1, split: 'A' },
+ { val: [2, 11], split: 'B' },
+ { val: 3 },
+ { val: 4, split: 'A' },
+ { val: 5 },
+ { val: 6, split: 'A' },
+ { val: 7, split: null },
+ { val: 8, split: 'B' },
+ { val: [9, 99], split: '' },
+ ],
+ };
+
+ const result = runFn(table, {
+ inputColumnId: 'val',
+ outputColumnId: 'output',
+ by: ['split'],
+ metric: 'sum',
+ });
+ expect(result.rows.map((row) => row.output)).toEqual([
+ 1 + 4 + 6,
+ 2 + 11 + 8,
+ 3 + 5 + 7 + 9 + 99,
+ 1 + 4 + 6,
+ 3 + 5 + 7 + 9 + 99,
+ 1 + 4 + 6,
+ 3 + 5 + 7 + 9 + 99,
+ 2 + 11 + 8,
+ 3 + 5 + 7 + 9 + 99,
+ ]);
+
+ const result2 = runFn(table, {
+ inputColumnId: 'val',
+ outputColumnId: 'output',
+ by: ['split'],
+ metric: 'max',
+ });
+ expect(result2.rows.map((row) => row.output)).toEqual([6, 11, 99, 6, 99, 6, 99, 11, 99]);
+ });
+
+ it('calculates cumulative sum for multiple series by multiple split columns', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [
+ { id: 'val', name: 'val', meta: { type: 'number' } },
+ { id: 'split', name: 'split', meta: { type: 'string' } },
+ { id: 'split2', name: 'split2', meta: { type: 'string' } },
+ ],
+ rows: [
+ { val: 1, split: 'A', split2: 'C' },
+ { val: 2, split: 'B', split2: 'C' },
+ { val: 3, split2: 'C' },
+ { val: 4, split: 'A', split2: 'C' },
+ { val: 5 },
+ { val: 6, split: 'A', split2: 'D' },
+ { val: 7, split: 'B', split2: 'D' },
+ { val: 8, split: 'B', split2: 'D' },
+ ],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', by: ['split', 'split2'], metric: 'sum' }
+ );
+ expect(result.rows.map((row) => row.output)).toEqual([1 + 4, 2, 3, 1 + 4, 5, 6, 7 + 8, 7 + 8]);
+ });
+
+ it('splits separate series by the string representation of the cell values', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [
+ { id: 'val', name: 'val', meta: { type: 'number' } },
+ { id: 'split', name: 'split', meta: { type: 'string' } },
+ ],
+ rows: [
+ { val: 1, split: { anObj: 3 } },
+ { val: 2, split: { anotherObj: 5 } },
+ { val: 10, split: 5 },
+ { val: 11, split: '5' },
+ ],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', by: ['split'], metric: 'sum' }
+ );
+
+ expect(result.rows.map((row) => row.output)).toEqual([1 + 2, 1 + 2, 10 + 11, 10 + 11]);
+ });
+
+ it('casts values to number before calculating cumulative sum', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
+ rows: [{ val: 5 }, { val: '7' }, { val: '3' }, { val: 2 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'max' }
+ );
+ expect(result.rows.map((row) => row.output)).toEqual([7, 7, 7, 7]);
+ });
+
+ it('casts values to number before calculating metric for NaN like values', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
+ rows: [{ val: 5 }, { val: '7' }, { val: {} }, { val: 2 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'min' }
+ );
+ expect(result.rows.map((row) => row.output)).toEqual([NaN, NaN, NaN, NaN]);
+ });
+
+ it('skips undefined and null values', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
+ rows: [
+ { val: null },
+ { val: 7 },
+ { val: undefined },
+ { val: undefined },
+ { val: undefined },
+ { val: undefined },
+ { val: '3' },
+ { val: 2 },
+ { val: null },
+ ],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'average' }
+ );
+ expect(result.rows.map((row) => row.output)).toEqual([4, 4, 4, 4, 4, 4, 4, 4, 4]);
+ });
+
+ it('copies over meta information from the source column', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [
+ {
+ id: 'val',
+ name: 'val',
+ meta: {
+ type: 'number',
+
+ field: 'afield',
+ index: 'anindex',
+ params: { id: 'number', params: { pattern: '000' } },
+ source: 'synthetic',
+ sourceParams: {
+ some: 'params',
+ },
+ },
+ },
+ ],
+ rows: [{ val: 5 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'output', metric: 'sum' }
+ );
+ expect(result.columns).toContainEqual({
+ id: 'output',
+ name: 'output',
+ meta: {
+ type: 'number',
+
+ field: 'afield',
+ index: 'anindex',
+ params: { id: 'number', params: { pattern: '000' } },
+ source: 'synthetic',
+ sourceParams: {
+ some: 'params',
+ },
+ },
+ });
+ });
+
+ it('sets output name on output column if specified', () => {
+ const result = runFn(
+ {
+ type: 'datatable',
+ columns: [
+ {
+ id: 'val',
+ name: 'val',
+ meta: {
+ type: 'number',
+ },
+ },
+ ],
+ rows: [{ val: 5 }],
+ },
+ {
+ inputColumnId: 'val',
+ outputColumnId: 'output',
+ outputColumnName: 'Output name',
+ metric: 'min',
+ }
+ );
+ expect(result.columns).toContainEqual({
+ id: 'output',
+ name: 'Output name',
+ meta: { type: 'number' },
+ });
+ });
+
+ it('returns source table if input column does not exist', () => {
+ const input: Datatable = {
+ type: 'datatable',
+ columns: [
+ {
+ id: 'val',
+ name: 'val',
+ meta: {
+ type: 'number',
+ },
+ },
+ ],
+ rows: [{ val: 5 }],
+ };
+ expect(
+ runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output', metric: 'sum' })
+ ).toBe(input);
+ });
+
+ it('throws an error if output column exists already', () => {
+ expect(() =>
+ runFn(
+ {
+ type: 'datatable',
+ columns: [
+ {
+ id: 'val',
+ name: 'val',
+ meta: {
+ type: 'number',
+ },
+ },
+ ],
+ rows: [{ val: 5 }],
+ },
+ { inputColumnId: 'val', outputColumnId: 'val', metric: 'max' }
+ )
+ ).toThrow();
+ });
+});
diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts
index e1378a27bdfc2..0ec61b39608a0 100644
--- a/src/plugins/expressions/common/expression_functions/types.ts
+++ b/src/plugins/expressions/common/expression_functions/types.ts
@@ -18,6 +18,7 @@ import {
ExpressionFunctionCumulativeSum,
ExpressionFunctionDerivative,
ExpressionFunctionMovingAverage,
+ ExpressionFunctionOverallMetric,
} from './specs';
import { ExpressionAstFunction } from '../ast';
import { PersistableStateDefinition } from '../../../kibana_utils/common';
@@ -119,6 +120,7 @@ export interface ExpressionFunctionDefinitions {
var: ExpressionFunctionVar;
theme: ExpressionFunctionTheme;
cumulative_sum: ExpressionFunctionCumulativeSum;
+ overall_metric: ExpressionFunctionOverallMetric;
derivative: ExpressionFunctionDerivative;
moving_average: ExpressionFunctionMovingAverage;
}
diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts
index a8839c9b0d71e..f7afc12aa96ba 100644
--- a/src/plugins/expressions/common/service/expressions_services.ts
+++ b/src/plugins/expressions/common/service/expressions_services.ts
@@ -29,6 +29,7 @@ import {
derivative,
movingAverage,
mapColumn,
+ overallMetric,
math,
} from '../expression_functions';
@@ -340,6 +341,7 @@ export class ExpressionsService implements PersistableStateService {
return Object.values(operationDefinitionMap)
.filter(({ hidden }) => !hidden)
+ .filter(
+ (operationDefinition) =>
+ !('selectionStyle' in operationDefinition) ||
+ operationDefinition.selectionStyle !== 'hidden'
+ )
.filter(({ type }) => fieldByOperation[type]?.size || operationWithoutField.has(type))
.sort((op1, op2) => {
return op1.displayName.localeCompare(op2.displayName);
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts
index 815acb8c4169f..a7741bc60d646 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/index.ts
@@ -9,3 +9,13 @@ export { counterRateOperation, CounterRateIndexPatternColumn } from './counter_r
export { cumulativeSumOperation, CumulativeSumIndexPatternColumn } from './cumulative_sum';
export { derivativeOperation, DerivativeIndexPatternColumn } from './differences';
export { movingAverageOperation, MovingAverageIndexPatternColumn } from './moving_average';
+export {
+ overallSumOperation,
+ OverallSumIndexPatternColumn,
+ overallMinOperation,
+ OverallMinIndexPatternColumn,
+ overallMaxOperation,
+ OverallMaxIndexPatternColumn,
+ overallAverageOperation,
+ OverallAverageIndexPatternColumn,
+} from './overall_metric';
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx
new file mode 100644
index 0000000000000..21ec5387b3853
--- /dev/null
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx
@@ -0,0 +1,224 @@
+/*
+ * Copyright 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 { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types';
+import { optionallHistogramBasedOperationToExpression } from './utils';
+import { OperationDefinition } from '..';
+import { getFormatFromPreviousColumn } from '../helpers';
+
+type OverallMetricIndexPatternColumn = FormattedIndexPatternColumn &
+ ReferenceBasedIndexPatternColumn & {
+ operationType: T;
+ };
+
+export type OverallSumIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_sum'>;
+export type OverallMinIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_min'>;
+export type OverallMaxIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_max'>;
+export type OverallAverageIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_average'>;
+
+function buildOverallMetricOperation>({
+ type,
+ displayName,
+ ofName,
+ description,
+ metric,
+}: {
+ type: T['operationType'];
+ displayName: string;
+ ofName: (name?: string) => string;
+ description: string;
+ metric: string;
+}): OperationDefinition {
+ return {
+ type,
+ priority: 1,
+ displayName,
+ input: 'fullReference',
+ selectionStyle: 'hidden',
+ requiredReferences: [
+ {
+ input: ['field', 'managedReference', 'fullReference'],
+ validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed,
+ },
+ ],
+ getPossibleOperation: () => {
+ return {
+ dataType: 'number',
+ isBucketed: false,
+ scale: 'ratio',
+ };
+ },
+ getDefaultLabel: (column, indexPattern, columns) => {
+ const ref = columns[column.references[0]];
+ return ofName(
+ ref && 'sourceField' in ref
+ ? indexPattern.getFieldByName(ref.sourceField)?.displayName
+ : undefined
+ );
+ },
+ toExpression: (layer, columnId) => {
+ return optionallHistogramBasedOperationToExpression(layer, columnId, 'overall_metric', {
+ metric: [metric],
+ });
+ },
+ buildColumn: ({ referenceIds, previousColumn, layer, indexPattern }, columnParams) => {
+ const ref = layer.columns[referenceIds[0]];
+ return {
+ label: ofName(
+ ref && 'sourceField' in ref
+ ? indexPattern.getFieldByName(ref.sourceField)?.displayName
+ : undefined
+ ),
+ dataType: 'number',
+ operationType: 'overall_sum',
+ isBucketed: false,
+ scale: 'ratio',
+ references: referenceIds,
+ params: getFormatFromPreviousColumn(previousColumn),
+ } as T;
+ },
+ isTransferable: () => {
+ return true;
+ },
+ filterable: false,
+ shiftable: false,
+ documentation: {
+ section: 'calculation',
+ signature: i18n.translate('xpack.lens.indexPattern.overall_metric', {
+ defaultMessage: 'metric: number',
+ }),
+ description,
+ },
+ };
+}
+
+export const overallSumOperation = buildOverallMetricOperation({
+ type: 'overall_sum',
+ displayName: i18n.translate('xpack.lens.indexPattern.overallSum', {
+ defaultMessage: 'Overall sum',
+ }),
+ ofName: (name?: string) => {
+ return i18n.translate('xpack.lens.indexPattern.overallSumOf', {
+ defaultMessage: 'Overall sum of {name}',
+ values: {
+ name:
+ name ??
+ i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
+ defaultMessage: '(incomplete)',
+ }),
+ },
+ });
+ },
+ metric: 'sum',
+ description: i18n.translate('xpack.lens.indexPattern.overall_sum.documentation', {
+ defaultMessage: `
+Calculates the sum of a metric of all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
+Other dimensions breaking down the data like top values or filter are treated as separate series.
+
+If no date histograms or interval functions are used in the current chart, \`overall_sum\` is calculating the sum over all dimensions no matter the used function.
+
+Example: Percentage of total
+\`sum(bytes) / overall_sum(sum(bytes))\`
+ `,
+ }),
+});
+
+export const overallMinOperation = buildOverallMetricOperation({
+ type: 'overall_min',
+ displayName: i18n.translate('xpack.lens.indexPattern.overallMin', {
+ defaultMessage: 'Overall min',
+ }),
+ ofName: (name?: string) => {
+ return i18n.translate('xpack.lens.indexPattern.overallMinOf', {
+ defaultMessage: 'Overall min of {name}',
+ values: {
+ name:
+ name ??
+ i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
+ defaultMessage: '(incomplete)',
+ }),
+ },
+ });
+ },
+ metric: 'min',
+ description: i18n.translate('xpack.lens.indexPattern.overall_min.documentation', {
+ defaultMessage: `
+Calculates the minimum of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
+Other dimensions breaking down the data like top values or filter are treated as separate series.
+
+If no date histograms or interval functions are used in the current chart, \`overall_min\` is calculating the minimum over all dimensions no matter the used function
+
+Example: Percentage of range
+\`(sum(bytes) - overall_min(sum(bytes)) / (overall_max(bytes) - overall_min(bytes))\`
+ `,
+ }),
+});
+
+export const overallMaxOperation = buildOverallMetricOperation({
+ type: 'overall_max',
+ displayName: i18n.translate('xpack.lens.indexPattern.overallMax', {
+ defaultMessage: 'Overall max',
+ }),
+ ofName: (name?: string) => {
+ return i18n.translate('xpack.lens.indexPattern.overallMaxOf', {
+ defaultMessage: 'Overall max of {name}',
+ values: {
+ name:
+ name ??
+ i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
+ defaultMessage: '(incomplete)',
+ }),
+ },
+ });
+ },
+ metric: 'max',
+ description: i18n.translate('xpack.lens.indexPattern.overall_max.documentation', {
+ defaultMessage: `
+Calculates the maximum of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
+Other dimensions breaking down the data like top values or filter are treated as separate series.
+
+If no date histograms or interval functions are used in the current chart, \`overall_max\` is calculating the maximum over all dimensions no matter the used function
+
+Example: Percentage of range
+\`(sum(bytes) - overall_min(sum(bytes)) / (overall_max(bytes) - overall_min(bytes))\`
+ `,
+ }),
+});
+
+export const overallAverageOperation = buildOverallMetricOperation(
+ {
+ type: 'overall_average',
+ displayName: i18n.translate('xpack.lens.indexPattern.overallMax', {
+ defaultMessage: 'Overall max',
+ }),
+ ofName: (name?: string) => {
+ return i18n.translate('xpack.lens.indexPattern.overallAverageOf', {
+ defaultMessage: 'Overall average of {name}',
+ values: {
+ name:
+ name ??
+ i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
+ defaultMessage: '(incomplete)',
+ }),
+ },
+ });
+ },
+ metric: 'average',
+ description: i18n.translate('xpack.lens.indexPattern.overall_average.documentation', {
+ defaultMessage: `
+Calculates the average of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
+Other dimensions breaking down the data like top values or filter are treated as separate series.
+
+If no date histograms or interval functions are used in the current chart, \`overall_average\` is calculating the average over all dimensions no matter the used function
+
+Example: Divergence from the mean:
+\`sum(bytes) - overall_average(sum(bytes))\`
+ `,
+ }),
+ }
+);
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts
index 1f4f097c6a7fb..03b9d6c07709c 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts
@@ -134,3 +134,35 @@ export function dateBasedOperationToExpression(
},
];
}
+
+/**
+ * Creates an expression ast for a date based operation (cumulative sum, derivative, moving average, counter rate)
+ */
+export function optionallHistogramBasedOperationToExpression(
+ layer: IndexPatternLayer,
+ columnId: string,
+ functionName: string,
+ additionalArgs: Record = {}
+): ExpressionFunctionAST[] {
+ const currentColumn = (layer.columns[columnId] as unknown) as ReferenceBasedIndexPatternColumn;
+ const buckets = layer.columnOrder.filter((colId) => layer.columns[colId].isBucketed);
+ const nonHistogramColumns = buckets.filter(
+ (colId) =>
+ layer.columns[colId].operationType !== 'date_histogram' &&
+ layer.columns[colId].operationType !== 'range'
+ )!;
+
+ return [
+ {
+ type: 'function',
+ function: functionName,
+ arguments: {
+ by: nonHistogramColumns.length === buckets.length ? [] : nonHistogramColumns,
+ inputColumnId: [currentColumn.references[0]],
+ outputColumnId: [columnId],
+ outputColumnName: [currentColumn.label],
+ ...additionalArgs,
+ },
+ },
+ ];
+}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts
index a7bf415817797..c38475f85f47e 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts
@@ -33,6 +33,14 @@ import {
DerivativeIndexPatternColumn,
movingAverageOperation,
MovingAverageIndexPatternColumn,
+ OverallSumIndexPatternColumn,
+ overallSumOperation,
+ OverallMinIndexPatternColumn,
+ overallMinOperation,
+ OverallMaxIndexPatternColumn,
+ overallMaxOperation,
+ OverallAverageIndexPatternColumn,
+ overallAverageOperation,
} from './calculations';
import { countOperation, CountIndexPatternColumn } from './count';
import {
@@ -71,6 +79,10 @@ export type IndexPatternColumn =
| CountIndexPatternColumn
| LastValueIndexPatternColumn
| CumulativeSumIndexPatternColumn
+ | OverallSumIndexPatternColumn
+ | OverallMinIndexPatternColumn
+ | OverallMaxIndexPatternColumn
+ | OverallAverageIndexPatternColumn
| CounterRateIndexPatternColumn
| DerivativeIndexPatternColumn
| MovingAverageIndexPatternColumn
@@ -98,6 +110,10 @@ export {
CounterRateIndexPatternColumn,
DerivativeIndexPatternColumn,
MovingAverageIndexPatternColumn,
+ OverallSumIndexPatternColumn,
+ OverallMinIndexPatternColumn,
+ OverallMaxIndexPatternColumn,
+ OverallAverageIndexPatternColumn,
} from './calculations';
export { CountIndexPatternColumn } from './count';
export { LastValueIndexPatternColumn } from './last_value';
@@ -126,6 +142,10 @@ const internalOperationDefinitions = [
movingAverageOperation,
mathOperation,
formulaOperation,
+ overallSumOperation,
+ overallMinOperation,
+ overallMaxOperation,
+ overallAverageOperation,
];
export { termsOperation } from './terms';
@@ -141,6 +161,10 @@ export {
counterRateOperation,
derivativeOperation,
movingAverageOperation,
+ overallSumOperation,
+ overallAverageOperation,
+ overallMaxOperation,
+ overallMinOperation,
} from './calculations';
export { formulaOperation } from './formula/formula';
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts
index aa46dd765bd8b..d55c5d3c00f17 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts
@@ -31,4 +31,8 @@ export {
CounterRateIndexPatternColumn,
DerivativeIndexPatternColumn,
MovingAverageIndexPatternColumn,
+ OverallSumIndexPatternColumn,
+ OverallMinIndexPatternColumn,
+ OverallMaxIndexPatternColumn,
+ OverallAverageIndexPatternColumn,
} from './definitions';
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts
index 7df096c27d9a0..2ed6e2b3a7bcb 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts
@@ -319,6 +319,22 @@ describe('getOperationTypesForField', () => {
"operationType": "moving_average",
"type": "fullReference",
},
+ Object {
+ "operationType": "overall_sum",
+ "type": "fullReference",
+ },
+ Object {
+ "operationType": "overall_min",
+ "type": "fullReference",
+ },
+ Object {
+ "operationType": "overall_max",
+ "type": "fullReference",
+ },
+ Object {
+ "operationType": "overall_average",
+ "type": "fullReference",
+ },
Object {
"field": "bytes",
"operationType": "min",
From a946f9bd3da37e44da480e61dd963bcf8b9f2a80 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Wed, 16 Jun 2021 10:23:31 +0200
Subject: [PATCH 11/98] add description and owner to kibana.json (#102238)
---
x-pack/plugins/lens/kibana.json | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json
index a5c19911f60b9..c5b3e72f39159 100644
--- a/x-pack/plugins/lens/kibana.json
+++ b/x-pack/plugins/lens/kibana.json
@@ -36,5 +36,10 @@
"kibanaUtils",
"kibanaReact",
"embeddable"
- ]
+ ],
+ "owner": {
+ "name": "Kibana App",
+ "githubTeam": "kibana-app"
+ },
+ "description": "Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana."
}
From aea2a384fd49e187dfc5edcf365faa0726d0b7ce Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Wed, 16 Jun 2021 10:24:13 +0200
Subject: [PATCH 12/98] Do not send other bucket request on exhaustive terms
list (#102097)
---
.../_terms_other_bucket_helper.test.ts | 62 +++++++++++++++++++
.../buckets/_terms_other_bucket_helper.ts | 6 +-
2 files changed, 67 insertions(+), 1 deletion(-)
diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts
index 2aa0d346afe34..523bbe1f01018 100644
--- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts
+++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts
@@ -174,6 +174,57 @@ const nestedTermResponse = {
status: 200,
};
+const exhaustiveNestedTermResponse = {
+ took: 10,
+ timed_out: false,
+ _shards: {
+ total: 1,
+ successful: 1,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 14005,
+ max_score: 0,
+ hits: [],
+ },
+ aggregations: {
+ '1': {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 8325,
+ buckets: [
+ {
+ '2': {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ { key: 'ios', doc_count: 2850 },
+ { key: 'win xp', doc_count: 2830 },
+ { key: '__missing__', doc_count: 1430 },
+ ],
+ },
+ key: 'US-with-dash',
+ doc_count: 2850,
+ },
+ {
+ '2': {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ { key: 'ios', doc_count: 1850 },
+ { key: 'win xp', doc_count: 1830 },
+ { key: '__missing__', doc_count: 130 },
+ ],
+ },
+ key: 'IN-with-dash',
+ doc_count: 2830,
+ },
+ ],
+ },
+ },
+ status: 200,
+};
+
const nestedTermResponseNoResults = {
took: 10,
timed_out: false,
@@ -326,6 +377,17 @@ describe('Terms Agg Other bucket helper', () => {
}
});
+ test('does not build query if sum_other_doc_count is 0 (exhaustive terms)', () => {
+ const aggConfigs = getAggConfigs(nestedTerm.aggs);
+ expect(
+ buildOtherBucketAgg(
+ aggConfigs,
+ aggConfigs.aggs[1] as IBucketAggConfig,
+ exhaustiveNestedTermResponse
+ )
+ ).toBeFalsy();
+ });
+
test('excludes exists filter for scripted fields', () => {
const aggConfigs = getAggConfigs(nestedTerm.aggs);
aggConfigs.aggs[1].params.field.scripted = true;
diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts
index 372d487bcf7a3..2a1cd873f6282 100644
--- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts
+++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts
@@ -156,6 +156,7 @@ export const buildOtherBucketAgg = (
};
let noAggBucketResults = false;
+ let exhaustiveBuckets = true;
// recursively create filters for all parent aggregation buckets
const walkBucketTree = (
@@ -175,6 +176,9 @@ export const buildOtherBucketAgg = (
const newAggIndex = aggIndex + 1;
const newAgg = bucketAggs[newAggIndex];
const currentAgg = bucketAggs[aggIndex];
+ if (aggIndex === index && agg && agg.sum_other_doc_count > 0) {
+ exhaustiveBuckets = false;
+ }
if (aggIndex < index) {
each(agg.buckets, (bucket: any, bucketObjKey) => {
const bucketKey = currentAgg.getKey(
@@ -223,7 +227,7 @@ export const buildOtherBucketAgg = (
walkBucketTree(0, response.aggregations, bucketAggs[0].id, [], '');
// bail if there were no bucket results
- if (noAggBucketResults) {
+ if (noAggBucketResults || exhaustiveBuckets) {
return false;
}
From 5c90bacce543f4549b57252f7efa8ea0925c09a5 Mon Sep 17 00:00:00 2001
From: Mikhail Shustov
Date: Wed, 16 Jun 2021 10:55:22 +0200
Subject: [PATCH 13/98] do not throw execa error when building ts refs
(#102154)
---
src/dev/typescript/build_ts_refs.ts | 16 ++++++++++++----
src/dev/typescript/run_type_check_cli.ts | 6 +++++-
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/dev/typescript/build_ts_refs.ts b/src/dev/typescript/build_ts_refs.ts
index 2e25827996e45..26425b7a3e61d 100644
--- a/src/dev/typescript/build_ts_refs.ts
+++ b/src/dev/typescript/build_ts_refs.ts
@@ -13,12 +13,20 @@ import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils';
export const REF_CONFIG_PATHS = [Path.resolve(REPO_ROOT, 'tsconfig.refs.json')];
-export async function buildAllTsRefs(log: ToolingLog) {
+export async function buildAllTsRefs(log: ToolingLog): Promise<{ failed: boolean }> {
for (const path of REF_CONFIG_PATHS) {
const relative = Path.relative(REPO_ROOT, path);
log.debug(`Building TypeScript projects refs for ${relative}...`);
- await execa(require.resolve('typescript/bin/tsc'), ['-b', relative, '--pretty'], {
- cwd: REPO_ROOT,
- });
+ const { failed, stdout } = await execa(
+ require.resolve('typescript/bin/tsc'),
+ ['-b', relative, '--pretty'],
+ {
+ cwd: REPO_ROOT,
+ reject: false,
+ }
+ );
+ log.info(stdout);
+ if (failed) return { failed };
}
+ return { failed: false };
}
diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts
index f95c230f44b9e..d9e9eb036fe0f 100644
--- a/src/dev/typescript/run_type_check_cli.ts
+++ b/src/dev/typescript/run_type_check_cli.ts
@@ -69,7 +69,11 @@ export async function runTypeCheckCli() {
process.exit();
}
- await buildAllTsRefs(log);
+ const { failed } = await buildAllTsRefs(log);
+ if (failed) {
+ log.error('Unable to build TS project refs');
+ process.exit(1);
+ }
const tscArgs = [
// composite project cannot be used with --noEmit
From aa97040bb6acb32b67b2a4c005ca905c50c5a5c9 Mon Sep 17 00:00:00 2001
From: Victor Martinez
Date: Wed, 16 Jun 2021 10:01:43 +0100
Subject: [PATCH 14/98] [APM-UI][e2e] discard CI builds more often (#102217)
---
.ci/end2end.groovy | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy
index 87b64437deafc..f1095f8035b6c 100644
--- a/.ci/end2end.groovy
+++ b/.ci/end2end.groovy
@@ -13,12 +13,12 @@ pipeline {
BASE_DIR = 'src/github.com/elastic/kibana'
HOME = "${env.WORKSPACE}"
E2E_DIR = 'x-pack/plugins/apm/e2e'
- PIPELINE_LOG_LEVEL = 'DEBUG'
+ PIPELINE_LOG_LEVEL = 'INFO'
KBN_OPTIMIZER_THEMES = 'v7light'
}
options {
timeout(time: 1, unit: 'HOURS')
- buildDiscarder(logRotator(numToKeepStr: '40', artifactNumToKeepStr: '20', daysToKeepStr: '30'))
+ buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '10', daysToKeepStr: '30'))
timestamps()
ansiColor('xterm')
disableResume()
From 037d7aeb8f11057e0b4696e9f6aeafd321b32220 Mon Sep 17 00:00:00 2001
From: Pablo Machado
Date: Wed, 16 Jun 2021 12:23:10 +0200
Subject: [PATCH 15/98] Enhance cases bulk deletion action dialog message
(#101403)
Differentiate the dialog message on the deletion of one item from the deletion of multiple items.
Simplifies CasesTableUtilityBar by handling the selection of multiple and single cases in the same way.
---
x-pack/plugins/cases/common/ui/types.ts | 2 +-
.../cases/public/common/translations.ts | 12 +++----
.../public/components/all_cases/actions.tsx | 4 +--
.../public/components/all_cases/columns.tsx | 1 -
.../components/all_cases/utility_bar.tsx | 33 +++++--------------
.../components/case_action_bar/actions.tsx | 3 +-
.../components/confirm_delete_case/index.tsx | 18 ++++------
.../confirm_delete_case/translations.ts | 26 +++++----------
.../containers/use_delete_cases.test.tsx | 6 ++--
9 files changed, 36 insertions(+), 69 deletions(-)
diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts
index 284f5e706292c..1dbb633e32adf 100644
--- a/x-pack/plugins/cases/common/ui/types.ts
+++ b/x-pack/plugins/cases/common/ui/types.ts
@@ -153,7 +153,7 @@ export interface ActionLicense {
export interface DeleteCase {
id: string;
type: CaseType | null;
- title?: string;
+ title: string;
}
export interface FieldMappings {
diff --git a/x-pack/plugins/cases/public/common/translations.ts b/x-pack/plugins/cases/public/common/translations.ts
index 85cfb60b1d6b8..f1bfde4cc4485 100644
--- a/x-pack/plugins/cases/public/common/translations.ts
+++ b/x-pack/plugins/cases/public/common/translations.ts
@@ -30,13 +30,11 @@ export const CANCEL = i18n.translate('xpack.cases.caseView.cancel', {
defaultMessage: 'Cancel',
});
-export const DELETE_CASE = i18n.translate('xpack.cases.confirmDeleteCase.deleteCase', {
- defaultMessage: 'Delete case',
-});
-
-export const DELETE_CASES = i18n.translate('xpack.cases.confirmDeleteCase.deleteCases', {
- defaultMessage: 'Delete cases',
-});
+export const DELETE_CASE = (quantity: number = 1) =>
+ i18n.translate('xpack.cases.confirmDeleteCase.deleteCase', {
+ values: { quantity },
+ defaultMessage: `Delete {quantity, plural, =1 {case} other {cases}}`,
+ });
export const NAME = i18n.translate('xpack.cases.caseView.name', {
defaultMessage: 'Name',
diff --git a/x-pack/plugins/cases/public/components/all_cases/actions.tsx b/x-pack/plugins/cases/public/components/all_cases/actions.tsx
index 8742b8fea23a4..4820b10308934 100644
--- a/x-pack/plugins/cases/public/components/all_cases/actions.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/actions.tsx
@@ -80,9 +80,9 @@ export const getActions = ({
makeInProgressAction,
closeCaseAction,
{
- description: i18n.DELETE_CASE,
+ description: i18n.DELETE_CASE(),
icon: 'trash',
- name: i18n.DELETE_CASE,
+ name: i18n.DELETE_CASE(),
onClick: deleteCaseOnClick,
type: 'icon',
'data-test-subj': 'action-delete',
diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx
index 947d405d188cf..a5a299851d975 100644
--- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx
@@ -306,7 +306,6 @@ export const useCasesColumns = ({
diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx
index d0981c38385e9..a2b4c14c0278a 100644
--- a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx
@@ -41,12 +41,8 @@ export const CasesTableUtilityBar: FunctionComponent = ({
refreshCases,
selectedCases,
}) => {
- const [deleteBulk, setDeleteBulk] = useState([]);
- const [deleteThisCase, setDeleteThisCase] = useState({
- title: '',
- id: '',
- type: null,
- });
+ const [deleteCases, setDeleteCases] = useState([]);
+
// Delete case
const {
dispatchResetIsDeleted,
@@ -86,24 +82,15 @@ export const CasesTableUtilityBar: FunctionComponent = ({
const toggleBulkDeleteModal = useCallback(
(cases: Case[]) => {
handleToggleModal();
- if (cases.length === 1) {
- const singleCase = cases[0];
- if (singleCase) {
- return setDeleteThisCase({
- id: singleCase.id,
- title: singleCase.title,
- type: singleCase.type,
- });
- }
- }
+
const convertToDeleteCases: DeleteCase[] = cases.map(({ id, title, type }) => ({
id,
title,
type,
}));
- setDeleteBulk(convertToDeleteCases);
+ setDeleteCases(convertToDeleteCases);
},
- [setDeleteBulk, handleToggleModal]
+ [setDeleteCases, handleToggleModal]
);
const handleUpdateCaseStatus = useCallback(
@@ -128,6 +115,7 @@ export const CasesTableUtilityBar: FunctionComponent = ({
),
[selectedCases, filterOptions.status, toggleBulkDeleteModal, handleUpdateCaseStatus]
);
+
return (
@@ -159,14 +147,11 @@ export const CasesTableUtilityBar: FunctionComponent = ({
0}
+ caseQuantity={deleteCases.length}
onCancel={handleToggleModal}
- onConfirm={handleOnDeleteConfirm.bind(
- null,
- deleteBulk.length > 0 ? deleteBulk : [deleteThisCase]
- )}
+ onConfirm={handleOnDeleteConfirm.bind(null, deleteCases)}
/>
);
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx
index 922ffd09aaac9..c2578dc3debdb 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx
+++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx
@@ -41,7 +41,7 @@ const ActionsComponent: React.FC = ({
{
disabled,
iconType: 'trash',
- label: i18n.DELETE_CASE,
+ label: i18n.DELETE_CASE(),
onClick: handleToggleModal,
},
...(currentExternalIncident != null && !isEmpty(currentExternalIncident?.externalUrl)
@@ -67,7 +67,6 @@ const ActionsComponent: React.FC = ({
void;
onConfirm: () => void;
}
@@ -20,7 +20,7 @@ interface ConfirmDeleteCaseModalProps {
const ConfirmDeleteCaseModalComp: React.FC = ({
caseTitle,
isModalVisible,
- isPlural,
+ caseQuantity = 1,
onCancel,
onConfirm,
}) => {
@@ -31,20 +31,14 @@ const ConfirmDeleteCaseModalComp: React.FC = ({
- {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION}
+ {i18n.CONFIRM_QUESTION(caseQuantity)}
);
};
diff --git a/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts b/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts
index 0400c4c7fef41..f8e4ab2a83a73 100644
--- a/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts
+++ b/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts
@@ -14,23 +14,15 @@ export const DELETE_TITLE = (caseTitle: string) =>
defaultMessage: 'Delete "{caseTitle}"',
});
-export const DELETE_THIS_CASE = (caseTitle: string) =>
- i18n.translate('xpack.cases.confirmDeleteCase.deleteThisCase', {
- defaultMessage: 'Delete this case',
+export const DELETE_SELECTED_CASES = (quantity: number, title: string) =>
+ i18n.translate('xpack.cases.confirmDeleteCase.selectedCases', {
+ values: { quantity, title },
+ defaultMessage: 'Delete "{quantity, plural, =1 {{title}} other {Selected {quantity} cases}}"',
});
-export const CONFIRM_QUESTION = i18n.translate('xpack.cases.confirmDeleteCase.confirmQuestion', {
- defaultMessage:
- 'By deleting this case, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?',
-});
-export const DELETE_SELECTED_CASES = i18n.translate('xpack.cases.confirmDeleteCase.selectedCases', {
- defaultMessage: 'Delete selected cases',
-});
-
-export const CONFIRM_QUESTION_PLURAL = i18n.translate(
- 'xpack.cases.confirmDeleteCase.confirmQuestionPlural',
- {
+export const CONFIRM_QUESTION = (quantity: number) =>
+ i18n.translate('xpack.cases.confirmDeleteCase.confirmQuestion', {
+ values: { quantity },
defaultMessage:
- 'By deleting these cases, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?',
- }
-);
+ 'By deleting {quantity, plural, =1 {this case} other {these cases}}, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?',
+ });
diff --git a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx
index e86ed0c036974..691af580b333a 100644
--- a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx
@@ -17,9 +17,9 @@ jest.mock('../common/lib/kibana');
describe('useDeleteCases', () => {
const abortCtrl = new AbortController();
const deleteObj = [
- { id: '1', type: CaseType.individual },
- { id: '2', type: CaseType.individual },
- { id: '3', type: CaseType.individual },
+ { id: '1', type: CaseType.individual, title: 'case 1' },
+ { id: '2', type: CaseType.individual, title: 'case 2' },
+ { id: '3', type: CaseType.individual, title: 'case 3' },
];
const deleteArr = ['1', '2', '3'];
it('init', async () => {
From 35e10ba77013715e381a2373790d67d94df825f1 Mon Sep 17 00:00:00 2001
From: Pete Harverson
Date: Wed, 16 Jun 2021 11:31:10 +0100
Subject: [PATCH 16/98] [ML] Adds optimizations for Logs UI anomaly detection
jobs (#102191)
* [ML] Adds optimizations for Logs UI anomaly detection jobs
* [ML] Increment version for log-entry-categories-count job
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../containers/logs/log_analysis/api/ml_setup_module_api.ts | 4 ++++
.../modules/log_entry_categories/module_descriptor.ts | 1 +
.../log_analysis/modules/log_entry_rate/module_descriptor.ts | 1 +
.../logs_ui_categories/ml/log_entry_categories_count.json | 4 ++--
4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts
index ea1567d6056f1..6304471e818fa 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts
@@ -21,6 +21,7 @@ interface RequestArgs {
jobOverrides?: SetupMlModuleJobOverrides[];
datafeedOverrides?: SetupMlModuleDatafeedOverrides[];
query?: object;
+ useDedicatedIndex?: boolean;
}
export const callSetupMlModuleAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
@@ -34,6 +35,7 @@ export const callSetupMlModuleAPI = async (requestArgs: RequestArgs, fetch: Http
jobOverrides = [],
datafeedOverrides = [],
query,
+ useDedicatedIndex = false,
} = requestArgs;
const response = await fetch(`/api/ml/modules/setup/${moduleId}`, {
@@ -48,6 +50,7 @@ export const callSetupMlModuleAPI = async (requestArgs: RequestArgs, fetch: Http
jobOverrides,
datafeedOverrides,
query,
+ useDedicatedIndex,
})
),
});
@@ -78,6 +81,7 @@ const setupMlModuleRequestParamsRT = rt.intersection([
startDatafeed: rt.boolean,
jobOverrides: rt.array(setupMlModuleJobOverridesRT),
datafeedOverrides: rt.array(setupMlModuleDatafeedOverridesRT),
+ useDedicatedIndex: rt.boolean,
}),
rt.exact(
rt.partial({
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/module_descriptor.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/module_descriptor.ts
index af2bd1802042a..6823ed173a740 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/module_descriptor.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/module_descriptor.ts
@@ -124,6 +124,7 @@ const setUpModule = async (
jobOverrides,
datafeedOverrides,
query,
+ useDedicatedIndex: true,
},
fetch
);
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/module_descriptor.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/module_descriptor.ts
index 9704afd80e9ea..c4c939d0ebb9d 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/module_descriptor.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/module_descriptor.ts
@@ -116,6 +116,7 @@ const setUpModule = async (
jobOverrides,
datafeedOverrides,
query,
+ useDedicatedIndex: true,
},
fetch
);
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
index ad7da3330bb6c..90f88275cb6d0 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
@@ -22,7 +22,7 @@
],
"per_partition_categorization": {
"enabled": true,
- "stop_on_warn": false
+ "stop_on_warn": true
}
},
"analysis_limits": {
@@ -38,6 +38,6 @@
},
"custom_settings": {
"created_by": "ml-module-logs-ui-categories",
- "job_revision": 1
+ "job_revision": 2
}
}
From d3c1f7c54d83eb5dbb27f699af16eb8637e82a2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Wed, 16 Jun 2021 14:38:04 +0200
Subject: [PATCH 17/98] [Observability] Updating header menu links across
Observability apps (#101472)
* [Observability] POC aligning menu links across apps
* [APM] Changed guttersize
* [APM] Replace placeholder button
* [Uptime] Remove icon from Settings header link
* [APM] Reordered anomaly detection and alerts
* [Logs] Remove icon from settings and change guttersize
* [Metrics] Remove icon from settings and change guttersize
* [Logs] Change button style of `isStreaming` state
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../app/RumDashboard/ActionMenu/index.tsx | 21 +++++++++++++++++--
.../alerting_popover_flyout.tsx | 3 ++-
.../anomaly_detection_setup_link.tsx | 9 ++++----
.../shared/apm_header_action_menu/index.tsx | 10 +++------
.../components/metrics_alert_dropdown.tsx | 8 ++++++-
.../components/alert_dropdown.tsx | 8 ++++++-
.../components/logging/log_datepicker.tsx | 15 +++++--------
.../infra/public/pages/logs/page_content.tsx | 4 ++--
.../infra/public/pages/metrics/index.tsx | 4 ++--
.../anomaly_detection_flyout.tsx | 2 ++
.../common/header/action_menu_content.tsx | 9 ++++----
.../alerts/toggle_alert_flyout_button.tsx | 2 ++
12 files changed, 61 insertions(+), 34 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx
index 6d04996b5f24c..20d930d28599f 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ActionMenu/index.tsx
@@ -55,26 +55,43 @@ export function UXActionMenu({
http?.basePath.get()
);
+ const kibana = useKibana();
+
return (
{ANALYZE_MESSAGE}}>
{ANALYZE_DATA}
+
+
+ {i18n.translate('xpack.apm.addDataButtonLabel', {
+ defaultMessage: 'Add data',
+ })}
+
+
);
diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
index 95acc55196c54..5b4f4e24af44d 100644
--- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
+++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/alerting_popover_flyout.tsx
@@ -66,7 +66,8 @@ export function AlertingPopoverAndFlyout({
const button = (
setPopoverOpen((prevState) => !prevState)}
diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx
index ade49bc7e3aa4..28c000310346d 100644
--- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx
+++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/anomaly_detection_setup_link.tsx
@@ -42,14 +42,15 @@ export function AnomalyDetectionSetupLink() {
return (
{canGetJobs && hasValidLicense ? (
) : (
-
+
)}
{ANOMALY_DETECTION_LINK_LABEL}
@@ -64,7 +65,7 @@ export function MissingJobsAlert({ environment }: { environment?: string }) {
anomalyDetectionJobsStatus,
} = useAnomalyDetectionJobsContext();
- const defaultIcon = ;
+ const defaultIcon = ;
if (anomalyDetectionJobsStatus === FETCH_STATUS.LOADING) {
return ;
@@ -92,7 +93,7 @@ export function MissingJobsAlert({ environment }: { environment?: string }) {
return (
-
+
);
}
diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/index.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/index.tsx
index 134941990a0f4..86f0d3fde1cd5 100644
--- a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/index.tsx
@@ -40,16 +40,13 @@ export function ApmHeaderActionMenu() {
}
return (
-
-
+
+
{i18n.translate('xpack.apm.settingsLinkLabel', {
defaultMessage: 'Settings',
})}
+ {canAccessML && }
{isAlertingAvailable && (
)}
- {canAccessML && }
{
panelPaddingSize="none"
anchorPosition="downLeft"
button={
-
+
}
diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
index 7cd6295cdcf40..66c77fbf875a4 100644
--- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
+++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_dropdown.tsx
@@ -83,7 +83,13 @@ export const AlertDropdown = () => {
+
}
diff --git a/x-pack/plugins/infra/public/components/logging/log_datepicker.tsx b/x-pack/plugins/infra/public/components/logging/log_datepicker.tsx
index b146da53caf6f..4f396ca7da495 100644
--- a/x-pack/plugins/infra/public/components/logging/log_datepicker.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_datepicker.tsx
@@ -6,7 +6,7 @@
*/
import React, { useCallback } from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiButtonEmpty } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
interface LogDatepickerProps {
@@ -49,24 +49,19 @@ export const LogDatepicker: React.FC = ({
{isStreaming ? (
-
+
-
+
) : (
-
+
-
+
)}
diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx
index 35e24700619f8..c7b145b4b0143 100644
--- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx
@@ -76,9 +76,9 @@ export const LogsPageContent: React.FunctionComponent = () => {
{setHeaderActionMenu && (
-
+
-
+
{settingsTabTitle}
diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx
index cda72e96012fe..e52d1e90d7efd 100644
--- a/x-pack/plugins/infra/public/pages/metrics/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx
@@ -84,9 +84,9 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
{setHeaderActionMenu && (
-
+
-
+
{settingsTabTitle}
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
index 5438209ae9c6b..d2cd4f87a5342 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout.tsx
@@ -51,6 +51,8 @@ export const AnomalyDetectionFlyout = () => {
return (
<>
+
@@ -72,12 +72,13 @@ export function ActionMenuContent(): React.ReactElement {
{ANALYZE_MESSAGE}}>
{ANALYZE_DATA}
diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
index fe507236569ec..a1b745d07924e 100644
--- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx
@@ -124,6 +124,8 @@ export const ToggleAlertFlyoutButtonComponent: React.FC = ({
Date: Wed, 16 Jun 2021 15:15:46 +0200
Subject: [PATCH 18/98] [Security solutions][Endpoint] Break long names on
remove trusted apps/event filters dialog (#102307)
* Break long names on remove trusted apps/event filters dialog.
* Removes wrong class
---
.../components/event_filter_delete_modal.tsx | 2 +-
.../trusted_app_deletion_dialog.test.tsx.snap | 18 ++++++++++++------
.../view/trusted_app_deletion_dialog.tsx | 2 +-
3 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.tsx
index 74a023965a57d..653469d304978 100644
--- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.tsx
@@ -91,7 +91,7 @@ export const EventFilterDeleteModal = memo<{}>(() => {
{eventFilter?.name} }}
+ values={{ name: {eventFilter?.name} }}
/>
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap
index 5ab58914ff8b1..0343ab62b9773 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap
@@ -56,9 +56,11 @@ exports[`TrustedAppDeletionDialog renders correctly when deletion failed 1`] = `
>
You are removing trusted application "
-
+
trusted app 3
-
+
".
@@ -158,9 +160,11 @@ exports[`TrustedAppDeletionDialog renders correctly when deletion is in progress
>
You are removing trusted application "
-
+
trusted app 3
-
+
".
@@ -265,9 +269,11 @@ exports[`TrustedAppDeletionDialog renders correctly when dialog started 1`] = `
>
You are removing trusted application "
-
+
trusted app 3
-
+
".
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
index bffd980610372..3afa2642eba12 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
@@ -45,7 +45,7 @@ const getTranslations = (entry: Immutable | undefined) => ({
{entry?.name} }}
+ values={{ name: {entry?.name} }}
/>
),
subMessage: (
From 78b803be2b6ce88f79af145c3dffed8a2f7c1206 Mon Sep 17 00:00:00 2001
From: Alexey Antonov
Date: Wed, 16 Jun 2021 16:16:56 +0300
Subject: [PATCH 19/98] Add telemetry for editor clicks events (#100664)
* Track editor clicks events
Closes: #98949
* add create and open telemetries
* add telemetry for dashboard
* remove hardcoded originatingApp for lens
* DashboardConstants.DASHBOARDS_ID -> DashboardConstants.DASHBOARD_ID
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
...ble-public.addpanelaction._constructor_.md | 3 +-
...lugins-embeddable-public.addpanelaction.md | 2 +-
...ns-embeddable-public.openaddpanelflyout.md | 3 +-
.../application/top_nav/dashboard_top_nav.tsx | 6 ++-
.../application/top_nav/editor_menu.tsx | 2 +-
.../public/lib/panel/embeddable_panel.tsx | 5 +-
.../add_panel/add_panel_action.ts | 5 +-
.../add_panel/add_panel_flyout.tsx | 32 +++++++++++--
.../add_panel/open_add_panel_flyout.tsx | 4 ++
src/plugins/embeddable/public/public.api.md | 5 +-
.../public/finder/saved_object_finder.tsx | 1 +
.../visualize_embeddable_factory.tsx | 3 ++
.../vis_types/vis_type_alias_registry.ts | 2 +
.../public/wizard/new_vis_modal.tsx | 2 +-
src/plugins/visualize/kibana.json | 3 +-
.../visualize/public/application/types.ts | 46 +++++++++++--------
.../application/utils/get_table_columns.tsx | 17 ++++++-
.../application/utils/get_top_nav_config.tsx | 19 +++++++-
src/plugins/visualize/public/plugin.ts | 43 ++++++++++-------
src/plugins/visualize/public/services.ts | 10 +++-
x-pack/plugins/lens/public/app_plugin/app.tsx | 1 +
.../lens/public/app_plugin/mounter.tsx | 3 +-
.../app_plugin/save_modal_container.tsx | 8 ++++
.../plugins/lens/public/app_plugin/types.ts | 2 +
x-pack/plugins/lens/public/plugin.ts | 3 +-
x-pack/plugins/lens/public/vis_type_alias.ts | 1 +
26 files changed, 175 insertions(+), 56 deletions(-)
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction._constructor_.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction._constructor_.md
index 388f0e064d866..e51c465e912e6 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction._constructor_.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction._constructor_.md
@@ -9,7 +9,7 @@ Constructs a new instance of the `AddPanelAction` class
Signature:
```typescript
-constructor(getFactory: EmbeddableStart['getEmbeddableFactory'], getAllFactories: EmbeddableStart['getEmbeddableFactories'], overlays: OverlayStart, notifications: NotificationsStart, SavedObjectFinder: React.ComponentType);
+constructor(getFactory: EmbeddableStart['getEmbeddableFactory'], getAllFactories: EmbeddableStart['getEmbeddableFactories'], overlays: OverlayStart, notifications: NotificationsStart, SavedObjectFinder: React.ComponentType, reportUiCounter?: ((appName: string, type: import("@kbn/analytics").UiCounterMetricType, eventNames: string | string[], count?: number | undefined) => void) | undefined);
```
## Parameters
@@ -21,4 +21,5 @@ constructor(getFactory: EmbeddableStart['getEmbeddableFactory'], getAllFactories
| overlays | OverlayStart
| |
| notifications | NotificationsStart
| |
| SavedObjectFinder | React.ComponentType<any>
| |
+| reportUiCounter | ((appName: string, type: import("@kbn/analytics").UiCounterMetricType, eventNames: string | string[], count?: number | undefined) => void) | undefined
| |
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction.md
index 74a6c2b2183a2..947e506f72b43 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.addpanelaction.md
@@ -14,7 +14,7 @@ export declare class AddPanelAction implements Action
| Constructor | Modifiers | Description |
| --- | --- | --- |
-| [(constructor)(getFactory, getAllFactories, overlays, notifications, SavedObjectFinder)](./kibana-plugin-plugins-embeddable-public.addpanelaction._constructor_.md) | | Constructs a new instance of the AddPanelAction
class |
+| [(constructor)(getFactory, getAllFactories, overlays, notifications, SavedObjectFinder, reportUiCounter)](./kibana-plugin-plugins-embeddable-public.addpanelaction._constructor_.md) | | Constructs a new instance of the AddPanelAction
class |
## Properties
diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md
index 90caaa3035b34..db45b691b446e 100644
--- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md
+++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md
@@ -15,6 +15,7 @@ export declare function openAddPanelFlyout(options: {
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType;
showCreateNewMenu?: boolean;
+ reportUiCounter?: UsageCollectionStart['reportUiCounter'];
}): OverlayRef;
```
@@ -22,7 +23,7 @@ export declare function openAddPanelFlyout(options: {
| Parameter | Type | Description |
| --- | --- | --- |
-| options | {
embeddable: IContainer;
getFactory: EmbeddableStart['getEmbeddableFactory'];
getAllFactories: EmbeddableStart['getEmbeddableFactories'];
overlays: OverlayStart;
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType<any>;
showCreateNewMenu?: boolean;
}
| |
+| options | {
embeddable: IContainer;
getFactory: EmbeddableStart['getEmbeddableFactory'];
getAllFactories: EmbeddableStart['getEmbeddableFactories'];
overlays: OverlayStart;
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType<any>;
showCreateNewMenu?: boolean;
reportUiCounter?: UsageCollectionStart['reportUiCounter'];
}
| |
Returns:
diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx
index 1cfa39d5e0e79..e5f89bd6a8e90 100644
--- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx
+++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx
@@ -132,7 +132,7 @@ export function DashboardTopNav({
const trackUiMetric = usageCollection?.reportUiCounter.bind(
usageCollection,
- DashboardConstants.DASHBOARDS_ID
+ DashboardConstants.DASHBOARD_ID
);
useEffect(() => {
@@ -163,6 +163,7 @@ export function DashboardTopNav({
notifications: core.notifications,
overlays: core.overlays,
SavedObjectFinder: getSavedObjectFinder(core.savedObjects, uiSettings),
+ reportUiCounter: usageCollection?.reportUiCounter,
}),
}));
}
@@ -174,6 +175,7 @@ export function DashboardTopNav({
core.savedObjects,
core.overlays,
uiSettings,
+ usageCollection,
]);
const createNewVisType = useCallback(
@@ -183,7 +185,7 @@ export function DashboardTopNav({
if (visType) {
if (trackUiMetric) {
- trackUiMetric(METRIC_TYPE.CLICK, visType.name);
+ trackUiMetric(METRIC_TYPE.CLICK, `${visType.name}:create`);
}
if ('aliasPath' in visType) {
diff --git a/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx
index 90cf0fcd571a1..74d725bb4d104 100644
--- a/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx
+++ b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx
@@ -51,7 +51,7 @@ export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => {
const trackUiMetric = usageCollection?.reportUiCounter.bind(
usageCollection,
- DashboardConstants.DASHBOARDS_ID
+ DashboardConstants.DASHBOARD_ID
);
const createNewAggsBasedVis = useCallback(
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
index 1214625fe530f..8cf2de8c80743 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
@@ -14,6 +14,7 @@ import deepEqual from 'fast-deep-equal';
import { buildContextMenuForActions, UiActionsService, Action } from '../ui_actions';
import { CoreStart, OverlayStart } from '../../../../../core/public';
import { toMountPoint } from '../../../../kibana_react/public';
+import { UsageCollectionStart } from '../../../../usage_collection/public';
import { Start as InspectorStartContract } from '../inspector';
import {
@@ -62,6 +63,7 @@ interface Props {
SavedObjectFinder: React.ComponentType;
stateTransfer?: EmbeddableStateTransfer;
hideHeader?: boolean;
+ reportUiCounter?: UsageCollectionStart['reportUiCounter'];
}
interface State {
@@ -312,7 +314,8 @@ export class EmbeddablePanel extends React.Component {
this.props.getAllEmbeddableFactories,
this.props.overlays,
this.props.notifications,
- this.props.SavedObjectFinder
+ this.props.SavedObjectFinder,
+ this.props.reportUiCounter
),
inspectPanel: new InspectPanelAction(this.props.inspector),
removePanel: new RemovePanelAction(),
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
index 8b6f81a199c44..49be1c3ce0123 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
@@ -13,6 +13,7 @@ import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin';
import { ViewMode } from '../../../../types';
import { openAddPanelFlyout } from './open_add_panel_flyout';
import { IContainer } from '../../../../containers';
+import { UsageCollectionStart } from '../../../../../../../usage_collection/public';
export const ACTION_ADD_PANEL = 'ACTION_ADD_PANEL';
@@ -29,7 +30,8 @@ export class AddPanelAction implements Action {
private readonly getAllFactories: EmbeddableStart['getEmbeddableFactories'],
private readonly overlays: OverlayStart,
private readonly notifications: NotificationsStart,
- private readonly SavedObjectFinder: React.ComponentType
+ private readonly SavedObjectFinder: React.ComponentType,
+ private readonly reportUiCounter?: UsageCollectionStart['reportUiCounter']
) {}
public getDisplayName() {
@@ -60,6 +62,7 @@ export class AddPanelAction implements Action {
overlays: this.overlays,
notifications: this.notifications,
SavedObjectFinder: this.SavedObjectFinder,
+ reportUiCounter: this.reportUiCounter,
});
}
}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
index 6d6a68d7e5e2a..eb4f0b30c5110 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx
@@ -9,15 +9,17 @@
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { ReactElement } from 'react';
-import { CoreSetup } from 'src/core/public';
+import { METRIC_TYPE } from '@kbn/analytics';
+import { CoreSetup, SavedObjectAttributes, SimpleSavedObject } from 'src/core/public';
import { EuiContextMenuItem, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
-import { EmbeddableStart } from 'src/plugins/embeddable/public';
+import { EmbeddableFactory, EmbeddableStart } from 'src/plugins/embeddable/public';
import { IContainer } from '../../../../containers';
import { EmbeddableFactoryNotFoundError } from '../../../../errors';
import { SavedObjectFinderCreateNew } from './saved_object_finder_create_new';
import { SavedObjectEmbeddableInput } from '../../../../embeddables';
+import { UsageCollectionStart } from '../../../../../../../usage_collection/public';
interface Props {
onClose: () => void;
@@ -27,6 +29,7 @@ interface Props {
notifications: CoreSetup['notifications'];
SavedObjectFinder: React.ComponentType;
showCreateNewMenu?: boolean;
+ reportUiCounter?: UsageCollectionStart['reportUiCounter'];
}
interface State {
@@ -84,7 +87,12 @@ export class AddPanelFlyout extends React.Component {
}
};
- public onAddPanel = async (savedObjectId: string, savedObjectType: string, name: string) => {
+ public onAddPanel = async (
+ savedObjectId: string,
+ savedObjectType: string,
+ name: string,
+ so: SimpleSavedObject
+ ) => {
const factoryForSavedObjectType = [...this.props.getAllFactories()].find(
(factory) =>
factory.savedObjectMetaData && factory.savedObjectMetaData.type === savedObjectType
@@ -98,9 +106,27 @@ export class AddPanelFlyout extends React.Component {
{ savedObjectId }
);
+ this.doTelemetryForAddEvent(this.props.container.type, factoryForSavedObjectType, so);
+
this.showToast(name);
};
+ private doTelemetryForAddEvent(
+ appName: string,
+ factoryForSavedObjectType: EmbeddableFactory,
+ so: SimpleSavedObject
+ ) {
+ const { reportUiCounter } = this.props;
+
+ if (reportUiCounter) {
+ const type = factoryForSavedObjectType.savedObjectMetaData?.getSavedObjectSubType
+ ? factoryForSavedObjectType.savedObjectMetaData.getSavedObjectSubType(so)
+ : factoryForSavedObjectType.type;
+
+ reportUiCounter(appName, METRIC_TYPE.CLICK, `${type}:add`);
+ }
+ }
+
private getCreateMenuItems(): ReactElement[] {
return [...this.props.getAllFactories()]
.filter(
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx
index f0c6e81644b3d..fe54b3d134aa0 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx
@@ -12,6 +12,7 @@ import { EmbeddableStart } from '../../../../../plugin';
import { toMountPoint } from '../../../../../../../kibana_react/public';
import { IContainer } from '../../../../containers';
import { AddPanelFlyout } from './add_panel_flyout';
+import { UsageCollectionStart } from '../../../../../../../usage_collection/public';
export function openAddPanelFlyout(options: {
embeddable: IContainer;
@@ -21,6 +22,7 @@ export function openAddPanelFlyout(options: {
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType;
showCreateNewMenu?: boolean;
+ reportUiCounter?: UsageCollectionStart['reportUiCounter'];
}): OverlayRef {
const {
embeddable,
@@ -30,6 +32,7 @@ export function openAddPanelFlyout(options: {
notifications,
SavedObjectFinder,
showCreateNewMenu,
+ reportUiCounter,
} = options;
const flyoutSession = overlays.openFlyout(
toMountPoint(
@@ -43,6 +46,7 @@ export function openAddPanelFlyout(options: {
getFactory={getFactory}
getAllFactories={getAllFactories}
notifications={notifications}
+ reportUiCounter={reportUiCounter}
SavedObjectFinder={SavedObjectFinder}
showCreateNewMenu={showCreateNewMenu}
/>
diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md
index 2a577e6167be5..af708f9a5e659 100644
--- a/src/plugins/embeddable/public/public.api.md
+++ b/src/plugins/embeddable/public/public.api.md
@@ -63,6 +63,7 @@ import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
import { Type } from '@kbn/config-schema';
import { TypeOf } from '@kbn/config-schema';
import { UiComponent } from 'src/plugins/kibana_utils/public';
+import { UiCounterMetricType } from '@kbn/analytics';
import { UnregisterCallback } from 'history';
import { URL } from 'url';
import { UserProvidedValues } from 'src/core/server/types';
@@ -95,7 +96,7 @@ export interface Adapters {
// @public (undocumented)
export class AddPanelAction implements Action_3 {
// Warning: (ae-forgotten-export) The symbol "React" needs to be exported by the entry point index.d.ts
- constructor(getFactory: EmbeddableStart_2['getEmbeddableFactory'], getAllFactories: EmbeddableStart_2['getEmbeddableFactories'], overlays: OverlayStart_2, notifications: NotificationsStart_2, SavedObjectFinder: React_2.ComponentType);
+ constructor(getFactory: EmbeddableStart_2['getEmbeddableFactory'], getAllFactories: EmbeddableStart_2['getEmbeddableFactories'], overlays: OverlayStart_2, notifications: NotificationsStart_2, SavedObjectFinder: React_2.ComponentType, reportUiCounter?: ((appName: string, type: import("@kbn/analytics").UiCounterMetricType, eventNames: string | string[], count?: number | undefined) => void) | undefined);
// (undocumented)
execute(context: ActionExecutionContext_2): Promise;
// (undocumented)
@@ -729,6 +730,7 @@ export function openAddPanelFlyout(options: {
notifications: NotificationsStart_2;
SavedObjectFinder: React.ComponentType;
showCreateNewMenu?: boolean;
+ reportUiCounter?: UsageCollectionStart['reportUiCounter'];
}): OverlayRef_2;
// Warning: (ae-missing-release-tag) "OutputSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -890,6 +892,7 @@ export const withEmbeddableSubscription: {
getIconForSavedObject(savedObject: SimpleSavedObject): IconType;
getTooltipForSavedObject?(savedObject: SimpleSavedObject): string;
showSavedObject?(savedObject: SimpleSavedObject): boolean;
+ getSavedObjectSubType?(savedObject: SimpleSavedObject): string;
includeFields?: string[];
}
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
index 3ccdfb7e47d70..872132416352f 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
@@ -104,6 +104,9 @@ export class VisualizeEmbeddableFactory
}
return visType.stage !== 'experimental';
},
+ getSavedObjectSubType: (savedObject) => {
+ return JSON.parse(savedObject.attributes.visState).type;
+ },
};
constructor(private readonly deps: VisualizeEmbeddableFactoryDeps) {}
diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
index 2be9358e28d1a..a8b00b15a1ede 100644
--- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
+++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts
@@ -7,6 +7,7 @@
*/
import { SavedObject } from '../../../../core/types/saved_objects';
+import { BaseVisType } from './base_vis_type';
export type VisualizationStage = 'experimental' | 'beta' | 'production';
@@ -23,6 +24,7 @@ export interface VisualizationListItem {
getSupportedTriggers?: () => string[];
typeTitle: string;
image?: string;
+ type?: BaseVisType | string;
}
export interface VisualizationsAppExtension {
diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
index 317f9d1bb363d..2620ae01aa15a 100644
--- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
+++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx
@@ -153,7 +153,7 @@ class NewVisModal extends React.Component {
+ const usageCollection = getUsageCollector();
+
+ if (usageCollection && visType) {
+ usageCollection.reportUiCounter(APP_NAME, METRIC_TYPE.CLICK, `${visType}:add`);
+ }
+};
const getBadge = (item: VisualizationListItem) => {
if (item.stage === 'beta') {
@@ -82,12 +93,16 @@ export const getTableColumns = (
defaultMessage: 'Title',
}),
sortable: true,
- render: (field: string, { editApp, editUrl, title, error }: VisualizationListItem) =>
+ render: (field: string, { editApp, editUrl, title, error, type }: VisualizationListItem) =>
// In case an error occurs i.e. the vis has wrong type, we render the vis but without the link
!error ? (
+ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
{
+ doTelemetryForAddEvent(typeof type === 'string' ? type : type?.name);
+ }}
data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`}
>
{field}
diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
index b7c7d63cef98f..da01f9d44879b 100644
--- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
+++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
+import { METRIC_TYPE } from '@kbn/analytics';
import { Capabilities } from 'src/core/public';
import { TopNavMenuData } from 'src/plugins/navigation/public';
@@ -29,7 +30,7 @@ import {
VisualizeAppStateContainer,
VisualizeEditorVisInstance,
} from '../types';
-import { VisualizeConstants } from '../visualize_constants';
+import { APP_NAME, VisualizeConstants } from '../visualize_constants';
import { getEditBreadcrumbs } from './breadcrumbs';
import { EmbeddableStateTransfer } from '../../../../embeddable/public';
@@ -92,10 +93,22 @@ export const getTopNavConfig = (
dashboard,
savedObjectsTagging,
presentationUtil,
+ usageCollection,
}: VisualizeServices
) => {
const { vis, embeddableHandler } = visInstance;
const savedVis = visInstance.savedVis;
+
+ const doTelemetryForSaveEvent = (visType: string) => {
+ if (usageCollection) {
+ usageCollection.reportUiCounter(
+ originatingApp ?? APP_NAME,
+ METRIC_TYPE.CLICK,
+ `${visType}:save`
+ );
+ }
+ };
+
/**
* Called when the user clicks "Save" button.
*/
@@ -394,6 +407,8 @@ export const getTopNavConfig = (
return { id: true };
}
+ doTelemetryForSaveEvent(vis.type.name);
+
// We're adding the viz to a library so we need to save it and then
// add to a dashboard if necessary
const response = await doSave(saveOptions);
@@ -503,6 +518,8 @@ export const getTopNavConfig = (
}
},
run: async () => {
+ doTelemetryForSaveEvent(vis.type.name);
+
if (!savedVis?.id) {
return createVisReference();
}
diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts
index 4b369e8be86ee..b5ddbdf6d10a3 100644
--- a/src/plugins/visualize/public/plugin.ts
+++ b/src/plugins/visualize/public/plugin.ts
@@ -6,10 +6,11 @@
* Side Public License, v 1.
*/
-import { BehaviorSubject } from 'rxjs';
import { i18n } from '@kbn/i18n';
-import { filter, map } from 'rxjs/operators';
import { createHashHistory } from 'history';
+import { BehaviorSubject } from 'rxjs';
+import { filter, map } from 'rxjs/operators';
+
import {
AppMountParameters,
AppUpdater,
@@ -18,29 +19,33 @@ import {
Plugin,
PluginInitializerContext,
ScopedHistory,
-} from 'kibana/public';
+ DEFAULT_APP_CATEGORIES,
+} from '../../../core/public';
-import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
import {
Storage,
createKbnUrlTracker,
createKbnUrlStateStorage,
withNotifyOnErrors,
} from '../../kibana_utils/public';
-import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public';
-import { NavigationPublicPluginStart as NavigationStart } from '../../navigation/public';
-import { SharePluginStart, SharePluginSetup } from '../../share/public';
-import { UrlForwardingSetup, UrlForwardingStart } from '../../url_forwarding/public';
-import { VisualizationsStart } from '../../visualizations/public';
+
import { VisualizeConstants } from './application/visualize_constants';
+import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public';
import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../home/public';
-import { VisualizeServices } from './application/types';
-import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
-import { SavedObjectsStart } from '../../saved_objects/public';
-import { EmbeddableStart } from '../../embeddable/public';
-import { DashboardStart } from '../../dashboard/public';
+
+import type { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
+import type { NavigationPublicPluginStart as NavigationStart } from '../../navigation/public';
+import type { SharePluginStart, SharePluginSetup } from '../../share/public';
+import type { UrlForwardingSetup, UrlForwardingStart } from '../../url_forwarding/public';
+import type { VisualizationsStart } from '../../visualizations/public';
+import type { VisualizeServices } from './application/types';
+import type { SavedObjectsStart } from '../../saved_objects/public';
+import type { EmbeddableStart } from '../../embeddable/public';
+import type { DashboardStart } from '../../dashboard/public';
import type { SavedObjectTaggingOssPluginStart } from '../../saved_objects_tagging_oss/public';
-import { setVisEditorsRegistry, setUISettings } from './services';
+import type { UsageCollectionStart } from '../../usage_collection/public';
+
+import { setVisEditorsRegistry, setUISettings, setUsageCollector } from './services';
import { createVisEditorsRegistry, VisEditorsRegistry } from './vis_editors_registry';
export interface VisualizePluginStartDependencies {
@@ -54,6 +59,7 @@ export interface VisualizePluginStartDependencies {
dashboard: DashboardStart;
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
presentationUtil: PresentationUtilPluginStart;
+ usageCollection?: UsageCollectionStart;
}
export interface VisualizePluginSetupDependencies {
@@ -202,6 +208,7 @@ export class VisualizePlugin
setHeaderActionMenu: params.setHeaderActionMenu,
savedObjectsTagging: pluginsStart.savedObjectsTaggingOss?.getTaggingApi(),
presentationUtil: pluginsStart.presentationUtil,
+ usageCollection: pluginsStart.usageCollection,
};
params.element.classList.add('visAppWrapper');
@@ -238,8 +245,12 @@ export class VisualizePlugin
} as VisualizePluginSetup;
}
- public start(core: CoreStart, plugins: VisualizePluginStartDependencies) {
+ public start(core: CoreStart, { usageCollection }: VisualizePluginStartDependencies) {
setVisEditorsRegistry(this.visEditorsRegistry);
+
+ if (usageCollection) {
+ setUsageCollector(usageCollection);
+ }
}
stop() {
diff --git a/src/plugins/visualize/public/services.ts b/src/plugins/visualize/public/services.ts
index 192aac3547eb2..97ff7923379b7 100644
--- a/src/plugins/visualize/public/services.ts
+++ b/src/plugins/visualize/public/services.ts
@@ -6,12 +6,18 @@
* Side Public License, v 1.
*/
-import { IUiSettingsClient } from '../../../core/public';
import { createGetterSetter } from '../../../plugins/kibana_utils/public';
-import { VisEditorsRegistry } from './vis_editors_registry';
+
+import type { IUiSettingsClient } from '../../../core/public';
+import type { VisEditorsRegistry } from './vis_editors_registry';
+import type { UsageCollectionStart } from '../../usage_collection/public';
export const [getUISettings, setUISettings] = createGetterSetter('UISettings');
+export const [getUsageCollector, setUsageCollector] = createGetterSetter(
+ 'UsageCollection'
+);
+
export const [
getVisEditorsRegistry,
setVisEditorsRegistry,
diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx
index 2256a05c51e12..95068204d5eb4 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.tsx
@@ -222,6 +222,7 @@ export function App({
persistedDoc: appState.persistedDoc,
onAppLeave,
redirectTo,
+ originatingApp: incomingState?.originatingApp,
...lensAppServices,
},
saveProps,
diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
index 46d2009756d2c..8e59f90c958f9 100644
--- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
@@ -52,7 +52,7 @@ export async function getLensServices(
startDependencies: LensPluginStartDependencies,
attributeService: () => Promise
): Promise {
- const { data, navigation, embeddable, savedObjectsTagging } = startDependencies;
+ const { data, navigation, embeddable, savedObjectsTagging, usageCollection } = startDependencies;
const storage = new Storage(localStorage);
const stateTransfer = embeddable?.getStateTransfer();
@@ -63,6 +63,7 @@ export async function getLensServices(
storage,
navigation,
stateTransfer,
+ usageCollection,
savedObjectsTagging,
attributeService: await attributeService(),
http: coreStart.http,
diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
index 27e8031f5fb6b..a65c8e6732e44 100644
--- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
@@ -9,6 +9,7 @@ import React, { useEffect, useState } from 'react';
import { ChromeStart, NotificationsStart } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { partition, uniq } from 'lodash';
+import { METRIC_TYPE } from '@kbn/analytics';
import { SaveModal } from './save_modal';
import { LensAppProps, LensAppServices } from './types';
import type { SaveProps } from './app';
@@ -112,6 +113,7 @@ export function SaveModalContainer({
attributeService,
redirectTo,
redirectToOrigin,
+ originatingApp,
getIsByValueMode: () => false,
onAppLeave: () => {},
},
@@ -178,6 +180,7 @@ export const runSaveLensVisualization = async (
lastKnownDoc?: Document;
getIsByValueMode: () => boolean;
persistedDoc?: Document;
+ originatingApp?: string;
} & ExtraProps &
LensAppServices,
saveProps: SaveProps,
@@ -190,6 +193,7 @@ export const runSaveLensVisualization = async (
const {
chrome,
initialInput,
+ originatingApp,
lastKnownDoc,
persistedDoc,
savedObjectsClient,
@@ -197,6 +201,7 @@ export const runSaveLensVisualization = async (
notifications,
stateTransfer,
attributeService,
+ usageCollection,
savedObjectsTagging,
getIsByValueMode,
redirectToOrigin,
@@ -209,6 +214,9 @@ export const runSaveLensVisualization = async (
persistedDoc && savedObjectsTagging
? savedObjectsTagging.ui.getTagIdsFromReferences(persistedDoc.references)
: [];
+ if (usageCollection) {
+ usageCollection.reportUiCounter(originatingApp || 'visualize', METRIC_TYPE.CLICK, 'lens:save');
+ }
let references = lastKnownDoc.references;
if (savedObjectsTagging) {
diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts
index d1e2d1cbdfc63..b253e76aa1407 100644
--- a/x-pack/plugins/lens/public/app_plugin/types.ts
+++ b/x-pack/plugins/lens/public/app_plugin/types.ts
@@ -18,6 +18,7 @@ import {
SavedObjectsStart,
} from '../../../../../src/core/public';
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
+import { UsageCollectionStart } from '../../../../../src/plugins/usage_collection/public';
import { DashboardStart } from '../../../../../src/plugins/dashboard/public';
import { LensEmbeddableInput } from '../editor_frame_service/embeddable/embeddable';
import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
@@ -97,6 +98,7 @@ export interface LensAppServices {
uiSettings: IUiSettingsClient;
application: ApplicationStart;
notifications: NotificationsStart;
+ usageCollection?: UsageCollectionStart;
stateTransfer: EmbeddableStateTransfer;
navigation: NavigationPublicPluginStart;
attributeService: LensAttributeService;
diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts
index 6d90691e2173a..328bea5def557 100644
--- a/x-pack/plugins/lens/public/plugin.ts
+++ b/x-pack/plugins/lens/public/plugin.ts
@@ -6,7 +6,7 @@
*/
import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
+import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_collection/public';
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { DashboardStart } from '../../../../src/plugins/dashboard/public';
@@ -81,6 +81,7 @@ export interface LensPluginStartDependencies {
savedObjectsTagging?: SavedObjectTaggingPluginStart;
presentationUtil: PresentationUtilPluginStart;
indexPatternFieldEditor: IndexPatternFieldEditorStart;
+ usageCollection?: UsageCollectionStart;
}
export interface LensPublicStart {
diff --git a/x-pack/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts
index b9a526c71180c..5b48ef8b31923 100644
--- a/x-pack/plugins/lens/public/vis_type_alias.ts
+++ b/x-pack/plugins/lens/public/vis_type_alias.ts
@@ -42,6 +42,7 @@ export const getLensAliasConfig = (): VisTypeAlias => ({
icon: 'lensApp',
stage: 'production',
savedObjectType: type,
+ type: 'lens',
typeTitle: i18n.translate('xpack.lens.visTypeAlias.type', { defaultMessage: 'Lens' }),
};
},
From a7b0391702c85f7262026a380763845be73d7fa7 Mon Sep 17 00:00:00 2001
From: Stacey Gammon
Date: Wed, 16 Jun 2021 09:28:50 -0400
Subject: [PATCH 20/98] Use export type instead of export to reduce bundle size
(#101796)
* Use export type instead of export to reduce bundle size
* Update legacy docs
* update docs again
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
...s-data-public.dataplugin._constructor_.md} | 4 +-
...a-plugin-plugins-data-public.dataplugin.md | 26 ++++++++
...n-plugins-data-public.dataplugin.setup.md} | 4 +-
...n-plugins-data-public.dataplugin.start.md} | 4 +-
...in-plugins-data-public.dataplugin.stop.md} | 4 +-
.../kibana-plugin-plugins-data-public.md | 2 +-
src/plugins/data/public/index.ts | 54 ++++++++--------
src/plugins/data/public/mocks.ts | 6 +-
src/plugins/data/public/public.api.md | 64 +++++++++----------
x-pack/plugins/graph/public/application.ts | 2 +-
10 files changed, 100 insertions(+), 70 deletions(-)
rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.plugin._constructor_.md => kibana-plugin-plugins-data-public.dataplugin._constructor_.md} (68%)
create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.md
rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.plugin.setup.md => kibana-plugin-plugins-data-public.dataplugin.setup.md} (76%)
rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.plugin.start.md => kibana-plugin-plugins-data-public.dataplugin.start.md} (70%)
rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.plugin.stop.md => kibana-plugin-plugins-data-public.dataplugin.stop.md} (52%)
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin._constructor_.md
similarity index 68%
rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin._constructor_.md
rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin._constructor_.md
index 64108a7c7be33..3eaf2176edf26 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin._constructor_.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin._constructor_.md
@@ -1,8 +1,8 @@
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [Plugin](./kibana-plugin-plugins-data-public.plugin.md) > [(constructor)](./kibana-plugin-plugins-data-public.plugin._constructor_.md)
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPlugin](./kibana-plugin-plugins-data-public.dataplugin.md) > [(constructor)](./kibana-plugin-plugins-data-public.dataplugin._constructor_.md)
-## Plugin.(constructor)
+## DataPlugin.(constructor)
Constructs a new instance of the `DataPublicPlugin` class
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.md
new file mode 100644
index 0000000000000..4b2cad7b42882
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPlugin](./kibana-plugin-plugins-data-public.dataplugin.md)
+
+## DataPlugin class
+
+Signature:
+
+```typescript
+export declare class DataPublicPlugin implements Plugin
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(initializerContext)](./kibana-plugin-plugins-data-public.dataplugin._constructor_.md) | | Constructs a new instance of the DataPublicPlugin
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [setup(core, { bfetch, expressions, uiActions, usageCollection, inspector })](./kibana-plugin-plugins-data-public.dataplugin.setup.md) | | |
+| [start(core, { uiActions })](./kibana-plugin-plugins-data-public.dataplugin.start.md) | | |
+| [stop()](./kibana-plugin-plugins-data-public.dataplugin.stop.md) | | |
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.setup.md
similarity index 76%
rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md
rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.setup.md
index 20181a5208b52..ab1f90c1ac104 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.setup.md
@@ -1,8 +1,8 @@
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [Plugin](./kibana-plugin-plugins-data-public.plugin.md) > [setup](./kibana-plugin-plugins-data-public.plugin.setup.md)
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPlugin](./kibana-plugin-plugins-data-public.dataplugin.md) > [setup](./kibana-plugin-plugins-data-public.dataplugin.setup.md)
-## Plugin.setup() method
+## DataPlugin.setup() method
Signature:
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.start.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.start.md
similarity index 70%
rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.start.md
rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.start.md
index 56934e8a29edd..4ea7ec8cd4f65 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.start.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.start.md
@@ -1,8 +1,8 @@
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [Plugin](./kibana-plugin-plugins-data-public.plugin.md) > [start](./kibana-plugin-plugins-data-public.plugin.start.md)
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPlugin](./kibana-plugin-plugins-data-public.dataplugin.md) > [start](./kibana-plugin-plugins-data-public.dataplugin.start.md)
-## Plugin.start() method
+## DataPlugin.start() method
Signature:
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.stop.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.stop.md
similarity index 52%
rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.stop.md
rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.stop.md
index 8b8b63db4e03a..b7067a01b4467 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.stop.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.dataplugin.stop.md
@@ -1,8 +1,8 @@
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [Plugin](./kibana-plugin-plugins-data-public.plugin.md) > [stop](./kibana-plugin-plugins-data-public.plugin.stop.md)
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DataPlugin](./kibana-plugin-plugins-data-public.dataplugin.md) > [stop](./kibana-plugin-plugins-data-public.dataplugin.stop.md)
-## Plugin.stop() method
+## DataPlugin.stop() method
Signature:
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
index 7f5a042e0ab81..7c023e756ebd5 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
@@ -11,6 +11,7 @@
| [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) | |
| [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) | |
| [AggParamType](./kibana-plugin-plugins-data-public.aggparamtype.md) | |
+| [DataPlugin](./kibana-plugin-plugins-data-public.dataplugin.md) | |
| [DuplicateIndexPatternError](./kibana-plugin-plugins-data-public.duplicateindexpatternerror.md) | |
| [FieldFormat](./kibana-plugin-plugins-data-public.fieldformat.md) | |
| [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) | |
@@ -19,7 +20,6 @@
| [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) | |
| [OptionedParamType](./kibana-plugin-plugins-data-public.optionedparamtype.md) | |
| [PainlessError](./kibana-plugin-plugins-data-public.painlesserror.md) | |
-| [Plugin](./kibana-plugin-plugins-data-public.plugin.md) | |
| [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) | |
| [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) | \* |
| [SearchTimeoutError](./kibana-plugin-plugins-data-public.searchtimeouterror.md) | Request Failure - When an entire multi request fails |
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index ba873952c9841..078dd3a9b7c5a 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -276,9 +276,8 @@ export { DuplicateIndexPatternError } from '../common/index_patterns/errors';
* Autocomplete query suggestions:
*/
-export {
+export type {
QuerySuggestion,
- QuerySuggestionTypes,
QuerySuggestionGetFn,
QuerySuggestionGetFnArgs,
QuerySuggestionBasic,
@@ -286,6 +285,7 @@ export {
AutocompleteStart,
} from './autocomplete';
+export { QuerySuggestionTypes } from './autocomplete';
/*
* Search:
*/
@@ -320,25 +320,23 @@ import {
tabifyGetColumns,
} from '../common';
-export {
+export { AggGroupLabels, AggGroupNames, METRIC_TYPES, BUCKET_TYPES } from '../common';
+
+export type {
// aggs
AggConfigSerialized,
- AggGroupLabels,
AggGroupName,
- AggGroupNames,
AggFunctionsMapping,
AggParam,
AggParamOption,
AggParamType,
AggConfigOptions,
- BUCKET_TYPES,
EsaggsExpressionFunctionDefinition,
IAggConfig,
IAggConfigs,
IAggType,
IFieldParamType,
IMetricAggType,
- METRIC_TYPES,
OptionedParamType,
OptionedValueProp,
ParsedInterval,
@@ -352,30 +350,23 @@ export {
export type { AggConfigs, AggConfig } from '../common';
-export {
+export type {
// search
ES_SEARCH_STRATEGY,
EsQuerySortValue,
- extractSearchSourceReferences,
- getEsPreference,
- getSearchParamsFromRequest,
IEsSearchRequest,
IEsSearchResponse,
IKibanaSearchRequest,
IKibanaSearchResponse,
- injectSearchSourceReferences,
ISearchSetup,
ISearchStart,
ISearchStartSearchSource,
ISearchGeneric,
ISearchSource,
- parseSearchSourceJSON,
SearchInterceptor,
SearchInterceptorDeps,
SearchRequest,
SearchSourceFields,
- SortDirection,
- SearchSessionState,
// expression functions and types
EsdslExpressionFunctionDefinition,
EsRawResponseExpressionTypeDefinition,
@@ -386,11 +377,21 @@ export {
TimeoutErrorMode,
PainlessError,
Reason,
+ WaitUntilNextSessionCompletesOptions,
+} from './search';
+
+export {
+ parseSearchSourceJSON,
+ injectSearchSourceReferences,
+ extractSearchSourceReferences,
+ getEsPreference,
+ getSearchParamsFromRequest,
noSearchSessionStorageCapabilityMessage,
SEARCH_SESSIONS_MANAGEMENT_ID,
waitUntilNextSessionCompletes$,
- WaitUntilNextSessionCompletesOptions,
isEsError,
+ SearchSessionState,
+ SortDirection,
} from './search';
export type {
@@ -438,33 +439,36 @@ export const search = {
* UI components
*/
-export {
- SearchBar,
+export type {
SearchBarProps,
StatefulSearchBarProps,
IndexPatternSelectProps,
- QueryStringInput,
QueryStringInputProps,
} from './ui';
+export { QueryStringInput, SearchBar } from './ui';
+
/**
* Types to be shared externally
* @public
*/
-export { Filter, Query, RefreshInterval, TimeRange } from '../common';
+export type { Filter, Query, RefreshInterval, TimeRange } from '../common';
export {
createSavedQueryService,
connectToQueryState,
syncQueryStateWithUrl,
- QueryState,
getDefaultQuery,
FilterManager,
+ TimeHistory,
+} from './query';
+
+export type {
+ QueryState,
SavedQuery,
SavedQueryService,
SavedQueryTimeFilter,
InputTimeRange,
- TimeHistory,
TimefilterContract,
TimeHistoryContract,
QueryStateChange,
@@ -472,7 +476,7 @@ export {
AutoRefreshDoneFn,
} from './query';
-export { AggsStart } from './search/aggs';
+export type { AggsStart } from './search/aggs';
export {
getTime,
@@ -496,7 +500,7 @@ export function plugin(initializerContext: PluginInitializerContext>;
-export type Start = jest.Mocked>;
+export type Setup = jest.Mocked>;
+export type Start = jest.Mocked>;
const autocompleteSetupMock: jest.Mocked = {
getQuerySuggestions: jest.fn(),
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 67534577d99fc..13352d183370b 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -67,7 +67,7 @@ import { Observable } from 'rxjs';
import { PackageInfo } from '@kbn/config';
import { Path } from 'history';
import { PeerCertificate } from 'tls';
-import { Plugin as Plugin_2 } from 'src/core/public';
+import { Plugin } from 'src/core/public';
import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public';
import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public';
import { PopoverAnchorPosition } from '@elastic/eui';
@@ -621,6 +621,22 @@ export type CustomFilter = Filter & {
query: any;
};
+// Warning: (ae-forgotten-export) The symbol "DataSetupDependencies" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "DataStartDependencies" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "DataPublicPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export class DataPlugin implements Plugin {
+ // Warning: (ae-forgotten-export) The symbol "ConfigSchema" needs to be exported by the entry point index.d.ts
+ constructor(initializerContext: PluginInitializerContext_2);
+ // (undocumented)
+ setup(core: CoreSetup, { bfetch, expressions, uiActions, usageCollection, inspector }: DataSetupDependencies): DataPublicPluginSetup;
+ // (undocumented)
+ start(core: CoreStart_2, { uiActions }: DataStartDependencies): DataPublicPluginStart;
+ // (undocumented)
+ stop(): void;
+ }
+
// Warning: (ae-missing-release-tag) "DataPublicPluginSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
@@ -2004,27 +2020,11 @@ export type PhrasesFilter = Filter & {
meta: PhrasesFilterMeta;
};
-// Warning: (ae-forgotten-export) The symbol "DataSetupDependencies" needs to be exported by the entry point index.d.ts
-// Warning: (ae-forgotten-export) The symbol "DataStartDependencies" needs to be exported by the entry point index.d.ts
-// Warning: (ae-missing-release-tag) "DataPublicPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export class Plugin implements Plugin_2 {
- // Warning: (ae-forgotten-export) The symbol "ConfigSchema" needs to be exported by the entry point index.d.ts
- constructor(initializerContext: PluginInitializerContext_2);
- // (undocumented)
- setup(core: CoreSetup, { bfetch, expressions, uiActions, usageCollection, inspector }: DataSetupDependencies): DataPublicPluginSetup;
- // (undocumented)
- start(core: CoreStart_2, { uiActions }: DataStartDependencies): DataPublicPluginStart;
- // (undocumented)
- stop(): void;
- }
-
// Warning: (ae-forgotten-export) The symbol "PluginInitializerContext" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "plugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export function plugin(initializerContext: PluginInitializerContext): Plugin;
+export function plugin(initializerContext: PluginInitializerContext): DataPlugin;
// Warning: (ae-missing-release-tag) "Query" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -2772,20 +2772,20 @@ export interface WaitUntilNextSessionCompletesOptions {
// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:407:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:407:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:407:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:430:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:431:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:434:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:408:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:408:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:408:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "IpAddress" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:428:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:431:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:432:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:435:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/search/session/session_service.ts:56:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts
diff --git a/x-pack/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts
index 0b80e18f3fdb2..26e86bbc3d886 100644
--- a/x-pack/plugins/graph/public/application.ts
+++ b/x-pack/plugins/graph/public/application.ts
@@ -31,7 +31,7 @@ import {
} from 'kibana/public';
// @ts-ignore
import { initGraphApp } from './app';
-import { Plugin as DataPlugin, IndexPatternsContract } from '../../../../src/plugins/data/public';
+import { DataPlugin, IndexPatternsContract } from '../../../../src/plugins/data/public';
import { LicensingPluginStart } from '../../licensing/public';
import { checkLicense } from '../common/check_license';
import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public';
From 3236f3fafa83447c99271a4260dd996b2424d128 Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Wed, 16 Jun 2021 06:30:31 -0700
Subject: [PATCH 21/98] skip flaky suite (#102283)
---
x-pack/test/api_integration/apis/ml/modules/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/test/api_integration/apis/ml/modules/index.ts b/x-pack/test/api_integration/apis/ml/modules/index.ts
index dae0044c47cca..f6c36c61b998c 100644
--- a/x-pack/test/api_integration/apis/ml/modules/index.ts
+++ b/x-pack/test/api_integration/apis/ml/modules/index.ts
@@ -13,6 +13,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
const fleetPackages = ['apache-0.5.0', 'nginx-0.5.0'];
// Failing: See https://github.com/elastic/kibana/issues/102282
+ // Failing: See https://github.com/elastic/kibana/issues/102283
describe.skip('modules', function () {
before(async () => {
for (const fleetPackage of fleetPackages) {
From 6b99e662cfced09f9cf28b056a1540b658457ce3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20S=C3=A1nchez?=
Date: Wed, 16 Jun 2021 15:32:26 +0200
Subject: [PATCH 22/98] Updates app_id to use integrations one instead of fleet
for back button link (#102312)
---
.../components/fleet_event_filters_card.tsx | 10 ++++++----
.../components/fleet_trusted_apps_card.tsx | 12 +++++++-----
2 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx
index be3cba5eb4318..5588cdbe81e3e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx
@@ -20,7 +20,7 @@ import {
GetExceptionSummaryResponse,
ListPageRouteState,
} from '../../../../../../../../common/endpoint/types';
-import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
+import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../../fleet/common';
import { MANAGEMENT_APP_ID } from '../../../../../../common/constants';
import { useToasts } from '../../../../../../../common/lib/kibana';
import { LinkWithIcon } from './link_with_icon';
@@ -68,19 +68,21 @@ export const FleetEventFiltersCard = memo(
}, [eventFiltersApi, toasts]);
const eventFiltersRouteState = useMemo(() => {
- const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
+ const fleetPackageCustomUrlPath = `#${
+ pagePathGetters.integration_details_custom({ pkgkey })[1]
+ }`;
return {
backButtonLabel: i18n.translate(
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
{ defaultMessage: 'Back to Endpoint Integration' }
),
onBackButtonNavigateTo: [
- FLEET_PLUGIN_ID,
+ INTEGRATIONS_PLUGIN_ID,
{
path: fleetPackageCustomUrlPath,
},
],
- backButtonUrl: getUrlForApp(FLEET_PLUGIN_ID, {
+ backButtonUrl: getUrlForApp(INTEGRATIONS_PLUGIN_ID, {
path: fleetPackageCustomUrlPath,
}),
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx
index ed3ba10c1e62b..f1c9cb13a27dc 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx
@@ -20,7 +20,7 @@ import {
ListPageRouteState,
GetExceptionSummaryResponse,
} from '../../../../../../../../common/endpoint/types';
-import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
+import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../../fleet/common';
import { MANAGEMENT_APP_ID } from '../../../../../../common/constants';
import { useToasts } from '../../../../../../../common/lib/kibana';
import { LinkWithIcon } from './link_with_icon';
@@ -68,24 +68,26 @@ export const FleetTrustedAppsCard = memo((
const trustedAppsListUrlPath = getTrustedAppsListPath();
const trustedAppRouteState = useMemo(() => {
- const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
+ const fleetPackageCustomUrlPath = `#${
+ pagePathGetters.integration_details_custom({ pkgkey })[1]
+ }`;
+
return {
backButtonLabel: i18n.translate(
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
{ defaultMessage: 'Back to Endpoint Integration' }
),
onBackButtonNavigateTo: [
- FLEET_PLUGIN_ID,
+ INTEGRATIONS_PLUGIN_ID,
{
path: fleetPackageCustomUrlPath,
},
],
- backButtonUrl: getUrlForApp(FLEET_PLUGIN_ID, {
+ backButtonUrl: getUrlForApp(INTEGRATIONS_PLUGIN_ID, {
path: fleetPackageCustomUrlPath,
}),
};
}, [getUrlForApp, pkgkey]);
-
return (
From 1b7a5a99cbf0f3ef0c4f656cf44e105fd1d4473d Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Wed, 16 Jun 2021 09:59:51 -0400
Subject: [PATCH 23/98] [Security Solution][Endpoint] Show Endpoint Host
Isolation status on endpoint list (#101961)
* Add endpoint isolation status for when multiple actions of different types are pending
* Refactored List to break out Agent status code to separate component
* Generator improvements for how actions are generated for Endpoints
* Add HTTP mock for fleet EPM packages (to silence console errors)
* new `.updateCommonInfo()` method to generator (to regenerate stateful data)
---
.../data_generators/base_data_generator.ts | 11 ++-
.../data_generators/fleet_action_generator.ts | 18 +++-
.../common/endpoint/generate_data.ts | 10 ++-
.../common/endpoint/index_data.ts | 85 +++++++++++++++---
.../endpoint_host_isolation_status.test.tsx | 56 ++++++++++++
.../endpoint_host_isolation_status.tsx | 66 +++++++++++---
.../endpoint_pending_actions.test.ts | 37 ++++++++
.../lib/endpoint_pending_actions/mocks.ts | 63 +++++++++++++
.../endpoint/http_handler_mock_factory.ts | 30 ++++---
.../management/pages/endpoint_hosts/mocks.ts | 34 ++++++-
.../pages/endpoint_hosts/store/action.ts | 7 +-
.../pages/endpoint_hosts/store/builders.ts | 3 +-
.../pages/endpoint_hosts/store/index.test.ts | 4 +
.../endpoint_hosts/store/middleware.test.ts | 76 +++++++++++-----
.../pages/endpoint_hosts/store/middleware.ts | 80 +++++++++++++----
.../pages/endpoint_hosts/store/reducer.ts | 17 +++-
.../pages/endpoint_hosts/store/selectors.ts | 39 ++++++++
.../management/pages/endpoint_hosts/types.ts | 11 ++-
.../components/endpoint_agent_status.test.tsx | 90 +++++++++++++++++++
.../view/components/endpoint_agent_status.tsx | 59 ++++++++++++
.../view/details/endpoint_details.tsx | 18 +---
.../pages/endpoint_hosts/view/index.tsx | 22 ++---
22 files changed, 718 insertions(+), 118 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.test.ts
create mode 100644 x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/mocks.ts
create mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts
index 35c976fbdfb1d..1f3d4307197f8 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts
@@ -48,9 +48,14 @@ export class BaseDataGenerator {
return new Date(now - this.randomChoice(DAY_OFFSETS)).toISOString();
}
- /** Generate either `true` or `false` */
- protected randomBoolean(): boolean {
- return this.random() < 0.5;
+ /**
+ * Generate either `true` or `false`. By default, the boolean is calculated by determining if a
+ * float is less than `0.5`, but that can be adjusted via the input argument
+ *
+ * @param isLessThan
+ */
+ protected randomBoolean(isLessThan: number = 0.5): boolean {
+ return this.random() < isLessThan;
}
/** generate random OS family value */
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
index af799de782f48..6cc5ab7f08447 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
@@ -13,7 +13,7 @@ import { EndpointAction, EndpointActionResponse, ISOLATION_ACTIONS } from '../ty
const ISOLATION_COMMANDS: ISOLATION_ACTIONS[] = ['isolate', 'unisolate'];
export class FleetActionGenerator extends BaseDataGenerator {
- /** Generate an Action */
+ /** Generate a random endpoint Action (isolate or unisolate) */
generate(overrides: DeepPartial = {}): EndpointAction {
const timeStamp = new Date(this.randomPastDate());
@@ -35,6 +35,14 @@ export class FleetActionGenerator extends BaseDataGenerator {
);
}
+ generateIsolateAction(overrides: DeepPartial = {}): EndpointAction {
+ return merge(this.generate({ data: { command: 'isolate' } }), overrides);
+ }
+
+ generateUnIsolateAction(overrides: DeepPartial = {}): EndpointAction {
+ return merge(this.generate({ data: { command: 'unisolate' } }), overrides);
+ }
+
/** Generates an action response */
generateResponse(overrides: DeepPartial = {}): EndpointActionResponse {
const timeStamp = new Date();
@@ -56,6 +64,14 @@ export class FleetActionGenerator extends BaseDataGenerator {
);
}
+ randomFloat(): number {
+ return this.random();
+ }
+
+ randomN(max: number): number {
+ return super.randomN(max);
+ }
+
protected randomIsolateCommand() {
return this.randomChoice(ISOLATION_COMMANDS);
}
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index 7e03d9b61fc10..b08d5649540db 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -422,6 +422,14 @@ export class EndpointDocGenerator extends BaseDataGenerator {
this.commonInfo.Endpoint.policy.applied.status = this.randomChoice(POLICY_RESPONSE_STATUSES);
}
+ /**
+ * Update the common host metadata - essentially creating an entire new endpoint metadata record
+ * when the `.generateHostMetadata()` is subsequently called
+ */
+ public updateCommonInfo() {
+ this.commonInfo = this.createHostData();
+ }
+
/**
* Parses an index and returns the data stream fields extracted from the index.
*
@@ -439,7 +447,7 @@ export class EndpointDocGenerator extends BaseDataGenerator {
private createHostData(): HostInfo {
const hostName = this.randomHostname();
- const isIsolated = this.randomBoolean();
+ const isIsolated = this.randomBoolean(0.3);
return {
agent: {
diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
index 4996d90288ca9..959db0d964aae 100644
--- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
@@ -13,6 +13,8 @@ import { AxiosResponse } from 'axios';
import { EndpointDocGenerator, Event, TreeOptions } from './generate_data';
import { firstNonNullValue } from './models/ecs_safety_helpers';
import {
+ AGENT_ACTIONS_INDEX,
+ AGENT_ACTIONS_RESULTS_INDEX,
AGENT_POLICY_API_ROUTES,
CreateAgentPolicyRequest,
CreateAgentPolicyResponse,
@@ -25,7 +27,7 @@ import {
PACKAGE_POLICY_API_ROUTES,
} from '../../../fleet/common';
import { policyFactory as policyConfigFactory } from './models/policy_config';
-import { HostMetadata } from './types';
+import { EndpointAction, HostMetadata } from './types';
import { KbnClientWithApiKeySupport } from '../../scripts/endpoint/kbn_client_with_api_key_support';
import { FleetAgentGenerator } from './data_generators/fleet_agent_generator';
import { FleetActionGenerator } from './data_generators/fleet_action_generator';
@@ -409,36 +411,97 @@ const indexFleetActionsForHost = async (
): Promise => {
const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } };
const agentId = endpointHost.elastic.agent.id;
+ const total = fleetActionGenerator.randomN(5);
- for (let i = 0; i < 5; i++) {
+ for (let i = 0; i < total; i++) {
// create an action
- const isolateAction = fleetActionGenerator.generate({
+ const action = fleetActionGenerator.generate({
data: { comment: 'data generator: this host is bad' },
});
- isolateAction.agents = [agentId];
+ action.agents = [agentId];
await esClient.index(
{
- index: '.fleet-actions',
- body: isolateAction,
+ index: AGENT_ACTIONS_INDEX,
+ body: action,
},
ES_INDEX_OPTIONS
);
// Create an action response for the above
- const unIsolateAction = fleetActionGenerator.generateResponse({
- action_id: isolateAction.action_id,
+ const actionResponse = fleetActionGenerator.generateResponse({
+ action_id: action.action_id,
agent_id: agentId,
- action_data: isolateAction.data,
+ action_data: action.data,
});
await esClient.index(
{
- index: '.fleet-actions-results',
- body: unIsolateAction,
+ index: AGENT_ACTIONS_RESULTS_INDEX,
+ body: actionResponse,
},
ES_INDEX_OPTIONS
);
}
+
+ // Add edge cases (maybe)
+ if (fleetActionGenerator.randomFloat() < 0.3) {
+ const randomFloat = fleetActionGenerator.randomFloat();
+
+ // 60% of the time just add either an Isoalte -OR- an UnIsolate action
+ if (randomFloat < 0.6) {
+ let action: EndpointAction;
+
+ if (randomFloat < 0.3) {
+ // add a pending isolation
+ action = fleetActionGenerator.generateIsolateAction({
+ '@timestamp': new Date().toISOString(),
+ });
+ } else {
+ // add a pending UN-isolation
+ action = fleetActionGenerator.generateUnIsolateAction({
+ '@timestamp': new Date().toISOString(),
+ });
+ }
+
+ action.agents = [agentId];
+
+ await esClient.index(
+ {
+ index: AGENT_ACTIONS_INDEX,
+ body: action,
+ },
+ ES_INDEX_OPTIONS
+ );
+ } else {
+ // Else (40% of the time) add a pending isolate AND pending un-isolate
+ const action1 = fleetActionGenerator.generateIsolateAction({
+ '@timestamp': new Date().toISOString(),
+ });
+ const action2 = fleetActionGenerator.generateUnIsolateAction({
+ '@timestamp': new Date().toISOString(),
+ });
+
+ action1.agents = [agentId];
+ action2.agents = [agentId];
+
+ await Promise.all([
+ esClient.index(
+ {
+ index: AGENT_ACTIONS_INDEX,
+ body: action1,
+ },
+ ES_INDEX_OPTIONS
+ ),
+ esClient.index(
+ {
+ index: AGENT_ACTIONS_INDEX,
+ body: action2,
+ },
+ ES_INDEX_OPTIONS
+ ),
+ ]);
+ }
+ }
};
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx
new file mode 100644
index 0000000000000..44405748b6373
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.test.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import {
+ EndpointHostIsolationStatus,
+ EndpointHostIsolationStatusProps,
+} from './endpoint_host_isolation_status';
+import { AppContextTestRender, createAppRootMockRenderer } from '../../../mock/endpoint';
+
+describe('when using the EndpointHostIsolationStatus component', () => {
+ let render: (
+ renderProps?: Partial
+ ) => ReturnType;
+
+ beforeEach(() => {
+ const appContext = createAppRootMockRenderer();
+ render = (renderProps = {}) =>
+ appContext.render(
+
+ );
+ });
+
+ it('should render `null` if not isolated and nothing is pending', () => {
+ const renderResult = render();
+ expect(renderResult.container.textContent).toBe('');
+ });
+
+ it('should show `Isolated` when no pending actions and isolated', () => {
+ const { getByTestId } = render({ isIsolated: true });
+ expect(getByTestId('test').textContent).toBe('Isolated');
+ });
+
+ it.each([
+ ['Isolating pending', { pendingIsolate: 2 }],
+ ['Unisolating pending', { pendingUnIsolate: 2 }],
+ ['4 actions pending', { isIsolated: true, pendingUnIsolate: 2, pendingIsolate: 2 }],
+ ])('should show %s}', (expectedLabel, componentProps) => {
+ const { getByTestId } = render(componentProps);
+ expect(getByTestId('test').textContent).toBe(expectedLabel);
+ // Validate that the text color is set to `subdued`
+ expect(getByTestId('test-pending').classList.contains('euiTextColor--subdued')).toBe(true);
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx
index 5cde22de69738..0fe3a8e4337cb 100644
--- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_status.tsx
@@ -6,8 +6,9 @@
*/
import React, { memo, useMemo } from 'react';
-import { EuiBadge, EuiTextColor } from '@elastic/eui';
+import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+import { useTestIdGenerator } from '../../../../management/components/hooks/use_test_id_generator';
export interface EndpointHostIsolationStatusProps {
isIsolated: boolean;
@@ -15,6 +16,7 @@ export interface EndpointHostIsolationStatusProps {
pendingIsolate?: number;
/** the count of pending unisoalte actions */
pendingUnIsolate?: number;
+ 'data-test-subj'?: string;
}
/**
@@ -23,7 +25,9 @@ export interface EndpointHostIsolationStatusProps {
* (`null` is returned)
*/
export const EndpointHostIsolationStatus = memo(
- ({ isIsolated, pendingIsolate = 0, pendingUnIsolate = 0 }) => {
+ ({ isIsolated, pendingIsolate = 0, pendingUnIsolate = 0, 'data-test-subj': dataTestSubj }) => {
+ const getTestId = useTestIdGenerator(dataTestSubj);
+
return useMemo(() => {
// If nothing is pending and host is not currently isolated, then render nothing
if (!isIsolated && !pendingIsolate && !pendingUnIsolate) {
@@ -33,7 +37,7 @@ export const EndpointHostIsolationStatus = memo
+
+
+
+
+
+
+
+
+
+ {pendingIsolate}
+
+
+
+
+
+ {pendingUnIsolate}
+
+
+ }
+ >
+
+
+
+
+
+ );
+ }
// Show 'pending [un]isolate' depending on what's pending
return (
-
-
+
+
{pendingIsolate ? (
);
- }, [isIsolated, pendingIsolate, pendingUnIsolate]);
+ }, [dataTestSubj, getTestId, isIsolated, pendingIsolate, pendingUnIsolate]);
}
);
diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.test.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.test.ts
new file mode 100644
index 0000000000000..a90f9a3508cd8
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.test.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { KibanaServices } from '../kibana';
+import { coreMock } from '../../../../../../../src/core/public/mocks';
+import { fetchPendingActionsByAgentId } from './endpoint_pending_actions';
+import { pendingActionsHttpMock, pendingActionsResponseMock } from './mocks';
+import { ACTION_STATUS_ROUTE } from '../../../../common/endpoint/constants';
+
+jest.mock('../kibana');
+
+describe('when using endpoint pending actions api service', () => {
+ let coreHttp: ReturnType['http'];
+
+ beforeEach(() => {
+ const coreStartMock = coreMock.createStart();
+ coreHttp = coreStartMock.http;
+ pendingActionsHttpMock(coreHttp);
+ (KibanaServices.get as jest.Mock).mockReturnValue(coreStartMock);
+ });
+
+ it('should call the endpont pending action status API', async () => {
+ const agentIdList = ['111-111', '222-222'];
+ const response = await fetchPendingActionsByAgentId(agentIdList);
+
+ expect(response).toEqual(pendingActionsResponseMock());
+ expect(coreHttp.get).toHaveBeenCalledWith(ACTION_STATUS_ROUTE, {
+ query: {
+ agent_ids: agentIdList,
+ },
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/mocks.ts
new file mode 100644
index 0000000000000..4c3822b07d88c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/mocks.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ PendingActionsRequestQuery,
+ PendingActionsResponse,
+} from '../../../../common/endpoint/types';
+import {
+ httpHandlerMockFactory,
+ ResponseProvidersInterface,
+} from '../../mock/endpoint/http_handler_mock_factory';
+import { ACTION_STATUS_ROUTE } from '../../../../common/endpoint/constants';
+
+export const pendingActionsResponseMock = (): PendingActionsResponse => ({
+ data: [
+ {
+ agent_id: '111-111',
+ pending_actions: {},
+ },
+ {
+ agent_id: '222-222',
+ pending_actions: {
+ isolate: 1,
+ },
+ },
+ ],
+});
+
+export type PendingActionsHttpMockInterface = ResponseProvidersInterface<{
+ pendingActions: () => PendingActionsResponse;
+}>;
+
+export const pendingActionsHttpMock = httpHandlerMockFactory([
+ {
+ id: 'pendingActions',
+ method: 'get',
+ path: ACTION_STATUS_ROUTE,
+ /** Will build a response based on the number of agent ids received. */
+ handler: (options) => {
+ const agentIds = (options.query as PendingActionsRequestQuery).agent_ids as string[];
+
+ if (agentIds.length) {
+ return {
+ data: agentIds.map((id, index) => ({
+ agent_id: id,
+ pending_actions:
+ index % 2 // index's of the array that are not divisible by 2 will will have `isolate: 1`
+ ? {
+ isolate: 1,
+ }
+ : {},
+ })),
+ };
+ }
+
+ return pendingActionsResponseMock();
+ },
+ },
+]);
diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts
index 2df16fc1e21b0..dc93ea8168a3f 100644
--- a/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts
@@ -7,12 +7,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
-import type {
- HttpFetchOptions,
- HttpFetchOptionsWithPath,
- HttpHandler,
- HttpStart,
-} from 'kibana/public';
+import type { HttpFetchOptions, HttpFetchOptionsWithPath, HttpStart } from 'kibana/public';
import { merge } from 'lodash';
import { act } from '@testing-library/react';
@@ -102,7 +97,7 @@ interface RouteMock) => any;
+ handler: (options: HttpFetchOptionsWithPath) => any;
/**
* A function that returns a promise. The API response will be delayed until this promise is
* resolved. This can be helpful when wanting to test an intermediate UI state while the API
@@ -203,14 +198,25 @@ export const httpHandlerMockFactory = pathMatchesPattern(handler.path, path));
if (routeMock) {
- markApiCallAsHandled(responseProvider[routeMock.id].mockDelay);
-
- await responseProvider[routeMock.id].mockDelay();
-
// Use the handler defined for the HTTP Mocked interface (not the one passed on input to
// the factory) for retrieving the response value because that one could have had its
// response value manipulated by the individual test case.
- return responseProvider[routeMock.id](...args);
+
+ markApiCallAsHandled(responseProvider[routeMock.id].mockDelay);
+ await responseProvider[routeMock.id].mockDelay();
+
+ const fetchOptions: HttpFetchOptionsWithPath = isHttpFetchOptionsWithPath(args[0])
+ ? args[0]
+ : {
+ // Ignore below is needed because the http service methods are defined via an overloaded interface.
+ // If the first argument is NOT fetch with options, then we know that its a string and `args` has
+ // a potential for being of `.length` 2.
+ // @ts-ignore
+ ...(args[1] || {}),
+ path: args[0],
+ };
+
+ return responseProvider[routeMock.id](fetchOptions);
} else if (priorMockedFunction) {
return priorMockedFunction(...args);
}
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts
index 3a3ad47f9f575..de05fa949b487 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts
@@ -23,7 +23,16 @@ import {
HOST_METADATA_GET_ROUTE,
HOST_METADATA_LIST_ROUTE,
} from '../../../../common/endpoint/constants';
-import { AGENT_POLICY_API_ROUTES, GetAgentPoliciesResponse } from '../../../../../fleet/common';
+import {
+ AGENT_POLICY_API_ROUTES,
+ EPM_API_ROUTES,
+ GetAgentPoliciesResponse,
+ GetPackagesResponse,
+} from '../../../../../fleet/common';
+import {
+ PendingActionsHttpMockInterface,
+ pendingActionsHttpMock,
+} from '../../../common/lib/endpoint_pending_actions/mocks';
type EndpointMetadataHttpMocksInterface = ResponseProvidersInterface<{
metadataList: () => HostResultList;
@@ -40,11 +49,15 @@ export const endpointMetadataHttpMocks = httpHandlerMockFactory {
- return {
+ const endpoint = {
metadata: generator.generateHostMetadata(),
host_status: HostStatus.UNHEALTHY,
query_strategy_version: MetadataQueryStrategyVersions.VERSION_2,
};
+
+ generator.updateCommonInfo();
+
+ return endpoint;
}),
total: 10,
request_page_size: 10,
@@ -88,6 +101,7 @@ export const endpointPolicyResponseHttpMock = httpHandlerMockFactory GetAgentPoliciesResponse;
+ packageList: () => GetPackagesResponse;
}>;
export const fleetApisHttpMock = httpHandlerMockFactory([
{
@@ -113,11 +127,24 @@ export const fleetApisHttpMock = httpHandlerMockFactory & {
+ payload: EndpointState['endpointPendingActions'];
+};
+
export type EndpointAction =
| ServerReturnedEndpointList
| ServerFailedToReturnEndpointList
@@ -186,4 +190,5 @@ export type EndpointAction =
| ServerFailedToReturnAgenstWithEndpointsTotal
| ServerFailedToReturnEndpointsTotal
| EndpointIsolationRequest
- | EndpointIsolationRequestStateChange;
+ | EndpointIsolationRequestStateChange
+ | EndpointPendingActionsStateChanged;
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
index 273b4279851fd..d43f361a0e6bb 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
@@ -7,7 +7,7 @@
import { Immutable } from '../../../../../common/endpoint/types';
import { DEFAULT_POLL_INTERVAL } from '../../../common/constants';
-import { createUninitialisedResourceState } from '../../../state';
+import { createLoadedResourceState, createUninitialisedResourceState } from '../../../state';
import { EndpointState } from '../types';
export const initialEndpointPageState = (): Immutable => {
@@ -53,5 +53,6 @@ export const initialEndpointPageState = (): Immutable => {
policyVersionInfo: undefined,
hostStatus: undefined,
isolationRequestState: createUninitialisedResourceState(),
+ endpointPendingActions: createLoadedResourceState(new Map()),
};
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
index 455c6538bcdf2..7f7c5f84f8bff 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
@@ -77,6 +77,10 @@ describe('EndpointList store concerns', () => {
isolationRequestState: {
type: 'UninitialisedResourceState',
},
+ endpointPendingActions: {
+ data: new Map(),
+ type: 'LoadedResourceState',
+ },
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
index 130f8a56fd026..52da30fabf95a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
@@ -43,6 +43,7 @@ import {
hostIsolationResponseMock,
} from '../../../../common/lib/endpoint_isolation/mocks';
import { FleetActionGenerator } from '../../../../../common/endpoint/data_generators/fleet_action_generator';
+import { endpointPageHttpMock } from '../mocks';
jest.mock('../../policy/store/services/ingest', () => ({
sendGetAgentConfigList: () => Promise.resolve({ items: [] }),
@@ -55,6 +56,7 @@ jest.mock('../../../../common/lib/kibana');
type EndpointListStore = Store, Immutable>;
describe('endpoint list middleware', () => {
+ const getKibanaServicesMock = KibanaServices.get as jest.Mock;
let fakeCoreStart: jest.Mocked;
let depsStart: DepsStartMock;
let fakeHttpServices: jest.Mocked;
@@ -69,6 +71,17 @@ describe('endpoint list middleware', () => {
return mockEndpointResultList({ request_page_size: 1, request_page_index: 1, total: 10 });
};
+ const dispatchUserChangedUrlToEndpointList = (locationOverrides: Partial = {}) => {
+ dispatch({
+ type: 'userChangedUrl',
+ payload: {
+ ...history.location,
+ pathname: getEndpointListPath({ name: 'endpointList' }),
+ ...locationOverrides,
+ },
+ });
+ };
+
beforeEach(() => {
fakeCoreStart = coreMock.createStart({ basePath: '/mock' });
depsStart = depsStartMock();
@@ -81,6 +94,7 @@ describe('endpoint list middleware', () => {
getState = store.getState;
dispatch = store.dispatch;
history = createBrowserHistory();
+ getKibanaServicesMock.mockReturnValue(fakeCoreStart);
});
it('handles `userChangedUrl`', async () => {
@@ -88,13 +102,7 @@ describe('endpoint list middleware', () => {
fakeHttpServices.post.mockResolvedValue(apiResponse);
expect(fakeHttpServices.post).not.toHaveBeenCalled();
- dispatch({
- type: 'userChangedUrl',
- payload: {
- ...history.location,
- pathname: getEndpointListPath({ name: 'endpointList' }),
- },
- });
+ dispatchUserChangedUrlToEndpointList();
await waitForAction('serverReturnedEndpointList');
expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', {
body: JSON.stringify({
@@ -111,13 +119,7 @@ describe('endpoint list middleware', () => {
expect(fakeHttpServices.post).not.toHaveBeenCalled();
// First change the URL
- dispatch({
- type: 'userChangedUrl',
- payload: {
- ...history.location,
- pathname: getEndpointListPath({ name: 'endpointList' }),
- },
- });
+ dispatchUserChangedUrlToEndpointList();
await waitForAction('serverReturnedEndpointList');
// Then request the Endpoint List
@@ -135,7 +137,6 @@ describe('endpoint list middleware', () => {
});
describe('handling of IsolateEndpointHost action', () => {
- const getKibanaServicesMock = KibanaServices.get as jest.Mock;
const dispatchIsolateEndpointHost = (action: ISOLATION_ACTIONS = 'isolate') => {
dispatch({
type: 'endpointIsolationRequest',
@@ -149,7 +150,6 @@ describe('endpoint list middleware', () => {
beforeEach(() => {
isolateApiResponseHandlers = hostIsolationHttpMocks(fakeHttpServices);
- getKibanaServicesMock.mockReturnValue(fakeCoreStart);
});
it('should set Isolation state to loading', async () => {
@@ -224,14 +224,7 @@ describe('endpoint list middleware', () => {
selected_endpoint: endpointList.hosts[0].metadata.agent.id,
});
const dispatchUserChangedUrl = () => {
- dispatch({
- type: 'userChangedUrl',
- payload: {
- ...history.location,
- pathname: '/endpoints',
- search: `?${search.split('?').pop()}`,
- },
- });
+ dispatchUserChangedUrlToEndpointList({ search: `?${search.split('?').pop()}` });
};
const fleetActionGenerator = new FleetActionGenerator(Math.random().toString());
@@ -300,4 +293,39 @@ describe('endpoint list middleware', () => {
expect(activityLogData).toEqual(getMockEndpointActivityLog());
});
});
+
+ describe('handle Endpoint Pending Actions state actions', () => {
+ let mockedApis: ReturnType;
+
+ beforeEach(() => {
+ mockedApis = endpointPageHttpMock(fakeHttpServices);
+ });
+
+ it('should include all agents ids from the list when calling API', async () => {
+ const loadingPendingActions = waitForAction('endpointPendingActionsStateChanged', {
+ validate: (action) => isLoadedResourceState(action.payload),
+ });
+
+ dispatchUserChangedUrlToEndpointList();
+ await loadingPendingActions;
+
+ expect(mockedApis.responseProvider.pendingActions).toHaveBeenCalledWith({
+ path: expect.any(String),
+ query: {
+ agent_ids: [
+ '6db499e5-4927-4350-abb8-d8318e7d0eec',
+ 'c082dda9-1847-4997-8eda-f1192d95bec3',
+ '8aa1cd61-cc25-4783-afb5-0eefc4919c07',
+ '47fe24c1-7370-419a-9732-3ff38bf41272',
+ '0d2b2fa7-a9cd-49fc-ad5f-0252c642290e',
+ 'f480092d-0445-4bf3-9c96-8a3d5cb97824',
+ '3850e676-0940-4c4b-aaca-571bd1bc66d9',
+ '46efcc7a-086a-47a3-8f09-c4ecd6d2d917',
+ 'afa55826-b81b-4440-a2ac-0644d77a3fc6',
+ '25b49e50-cb5c-43df-824f-67b8cf697d9d',
+ ],
+ },
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index aa0afe5ec980a..4f96223e8b789 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -34,8 +34,9 @@ import {
getActivityLogData,
getActivityLogDataPaging,
getLastLoadedActivityLogData,
+ detailsData,
} from './selectors';
-import { EndpointState, PolicyIds } from '../types';
+import { AgentIdsPendingActions, EndpointState, PolicyIds } from '../types';
import {
sendGetEndpointSpecificPackagePolicies,
sendGetEndpointSecurityPackage,
@@ -59,9 +60,13 @@ import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint_isol
import { AppAction } from '../../../../common/store/actions';
import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables';
import { ServerReturnedEndpointPackageInfo } from './action';
+import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions';
type EndpointPageStore = ImmutableMiddlewareAPI;
+// eslint-disable-next-line no-console
+const logError = console.error;
+
export const endpointMiddlewareFactory: ImmutableMiddlewareFactory = (
coreStart,
depsStart
@@ -110,6 +115,8 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory => {
})
).total;
} catch (error) {
- // eslint-disable-next-line no-console
- console.error(`error while trying to check for total endpoints`);
- // eslint-disable-next-line no-console
- console.error(error);
+ logError(`error while trying to check for total endpoints`);
+ logError(error);
}
return 0;
};
@@ -524,10 +528,8 @@ const doEndpointsExist = async (http: HttpStart): Promise => {
try {
return (await endpointsTotal(http)) > 0;
} catch (error) {
- // eslint-disable-next-line no-console
- console.error(`error while trying to check if endpoints exist`);
- // eslint-disable-next-line no-console
- console.error(error);
+ logError(`error while trying to check if endpoints exist`);
+ logError(error);
}
return false;
};
@@ -586,7 +588,51 @@ async function getEndpointPackageInfo(
});
} catch (error) {
// Ignore Errors, since this should not hinder the user's ability to use the UI
- // eslint-disable-next-line no-console
- console.error(error);
+ logError(error);
}
}
+
+/**
+ * retrieves the Endpoint pending actions for all of the existing endpoints being displayed on the list
+ * or the details tab.
+ *
+ * @param store
+ */
+const loadEndpointsPendingActions = async ({
+ getState,
+ dispatch,
+}: EndpointPageStore): Promise => {
+ const state = getState();
+ const detailsEndpoint = detailsData(state);
+ const listEndpoints = listData(state);
+ const agentsIds = new Set();
+
+ // get all agent ids for the endpoints in the list
+ if (detailsEndpoint) {
+ agentsIds.add(detailsEndpoint.elastic.agent.id);
+ }
+
+ for (const endpointInfo of listEndpoints) {
+ agentsIds.add(endpointInfo.metadata.elastic.agent.id);
+ }
+
+ if (agentsIds.size === 0) {
+ return;
+ }
+
+ try {
+ const { data: pendingActions } = await fetchPendingActionsByAgentId(Array.from(agentsIds));
+ const agentIdToPendingActions: AgentIdsPendingActions = new Map();
+
+ for (const pendingAction of pendingActions) {
+ agentIdToPendingActions.set(pendingAction.agent_id, pendingAction.pending_actions);
+ }
+
+ dispatch({
+ type: 'endpointPendingActionsStateChanged',
+ payload: createLoadedResourceState(agentIdToPendingActions),
+ });
+ } catch (error) {
+ logError(error);
+ }
+};
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
index b580664512eb6..9460c27dfe705 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EndpointDetailsActivityLogChanged } from './action';
+import { EndpointDetailsActivityLogChanged, EndpointPendingActionsStateChanged } from './action';
import {
isOnEndpointPage,
hasSelectedEndpoint,
@@ -41,6 +41,19 @@ const handleEndpointDetailsActivityLogChanged: CaseReducer = (
+ state,
+ action
+) => {
+ if (isOnEndpointPage(state)) {
+ return {
+ ...state,
+ endpointPendingActions: action.payload,
+ };
+ }
+ return state;
+};
+
/* eslint-disable-next-line complexity */
export const endpointListReducer: StateReducer = (state = initialEndpointPageState(), action) => {
if (action.type === 'serverReturnedEndpointList') {
@@ -141,6 +154,8 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
};
} else if (action.type === 'endpointDetailsActivityLogChanged') {
return handleEndpointDetailsActivityLogChanged(state, action);
+ } else if (action.type === 'endpointPendingActionsStateChanged') {
+ return handleEndpointPendingActionsStateChanged(state, action);
} else if (action.type === 'serverReturnedPoliciesForOnboarding') {
return {
...state,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
index 2b567d1ad53b5..d9be85377c81d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
@@ -18,6 +18,7 @@ import {
MetadataQueryStrategyVersions,
HostStatus,
ActivityLog,
+ HostMetadata,
} from '../../../../../common/endpoint/types';
import { EndpointState, EndpointIndexUIQueryParams } from '../types';
import { extractListPaginationParams } from '../../../common/routing';
@@ -36,6 +37,7 @@ import {
import { ServerApiError } from '../../../../common/types';
import { isEndpointHostIsolated } from '../../../../common/utils/validators';
+import { EndpointHostIsolationStatusProps } from '../../../../common/components/endpoint/host_isolation';
export const listData = (state: Immutable) => state.hosts;
@@ -412,3 +414,40 @@ export const getActivityLogError: (
export const getIsEndpointHostIsolated = createSelector(detailsData, (details) => {
return (details && isEndpointHostIsolated(details)) || false;
});
+
+export const getEndpointPendingActionsState = (
+ state: Immutable
+): Immutable => {
+ return state.endpointPendingActions;
+};
+
+/**
+ * Returns a function (callback) that can be used to retrieve the props for the `EndpointHostIsolationStatus`
+ * component for a given Endpoint
+ */
+export const getEndpointHostIsolationStatusPropsCallback: (
+ state: Immutable
+) => (endpoint: HostMetadata) => EndpointHostIsolationStatusProps = createSelector(
+ getEndpointPendingActionsState,
+ (pendingActionsState) => {
+ return (endpoint: HostMetadata) => {
+ let pendingIsolate = 0;
+ let pendingUnIsolate = 0;
+
+ if (isLoadedResourceState(pendingActionsState)) {
+ const endpointPendingActions = pendingActionsState.data.get(endpoint.elastic.agent.id);
+
+ if (endpointPendingActions) {
+ pendingIsolate = endpointPendingActions?.isolate ?? 0;
+ pendingUnIsolate = endpointPendingActions?.unisolate ?? 0;
+ }
+ }
+
+ return {
+ isIsolated: isEndpointHostIsolated(endpoint),
+ pendingIsolate,
+ pendingUnIsolate,
+ };
+ };
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
index eed2182d41809..59aa2bd15dd74 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
@@ -16,6 +16,7 @@ import {
MetadataQueryStrategyVersions,
HostStatus,
HostIsolationResponse,
+ EndpointPendingActions,
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
import { GetPackagesResponse } from '../../../../../fleet/common';
@@ -94,10 +95,18 @@ export interface EndpointState {
policyVersionInfo?: HostInfo['policy_info'];
/** The status of the host, which is mapped to the Elastic Agent status in Fleet */
hostStatus?: HostStatus;
- /* Host isolation state */
+ /** Host isolation request state for a single endpoint */
isolationRequestState: AsyncResourceState;
+ /**
+ * Holds a map of `agentId` to `EndpointPendingActions` that is used by both the list and details view
+ * Getting pending endpoint actions is "supplemental" data, so there is no need to show other Async
+ * states other than Loaded
+ */
+ endpointPendingActions: AsyncResourceState;
}
+export type AgentIdsPendingActions = Map;
+
/**
* packagePolicy contains a list of Package Policy IDs (received via Endpoint metadata policy response) mapped to a boolean whether they exist or not.
* agentPolicy contains a list of existing Package Policy Ids mapped to an associated Fleet parent Agent Config.
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx
new file mode 100644
index 0000000000000..9010bb5785c1d
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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 {
+ AppContextTestRender,
+ createAppRootMockRenderer,
+} from '../../../../../common/mock/endpoint';
+import { endpointPageHttpMock } from '../../mocks';
+import { act } from '@testing-library/react';
+import { EndpointAgentStatus, EndpointAgentStatusProps } from './endpoint_agent_status';
+import { HostMetadata, HostStatus } from '../../../../../../common/endpoint/types';
+import { isLoadedResourceState } from '../../../../state';
+import { KibanaServices } from '../../../../../common/lib/kibana';
+
+jest.mock('../../../../../common/lib/kibana');
+
+describe('When using the EndpointAgentStatus component', () => {
+ let render: (
+ props: EndpointAgentStatusProps
+ ) => Promise>;
+ let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction'];
+ let renderResult: ReturnType;
+ let httpMocks: ReturnType;
+ let endpointMeta: HostMetadata;
+
+ beforeEach(() => {
+ const mockedContext = createAppRootMockRenderer();
+
+ (KibanaServices.get as jest.Mock).mockReturnValue(mockedContext.startServices);
+ httpMocks = endpointPageHttpMock(mockedContext.coreStart.http);
+ waitForAction = mockedContext.middlewareSpy.waitForAction;
+ endpointMeta = httpMocks.responseProvider.metadataList().hosts[0].metadata;
+ render = async (props: EndpointAgentStatusProps) => {
+ renderResult = mockedContext.render( );
+ return renderResult;
+ };
+
+ act(() => {
+ mockedContext.history.push('/endpoints');
+ });
+ });
+
+ it.each([
+ ['Healthy', 'healthy'],
+ ['Unhealthy', 'unhealthy'],
+ ['Updating', 'updating'],
+ ['Offline', 'offline'],
+ ['Inactive', 'inactive'],
+ ['Unhealthy', 'someUnknownValueHere'],
+ ])('should show agent status of %s', async (expectedLabel, hostStatus) => {
+ await render({ hostStatus: hostStatus as HostStatus, endpointMetadata: endpointMeta });
+ expect(renderResult.getByTestId('rowHostStatus').textContent).toEqual(expectedLabel);
+ });
+
+ describe('and host is isolated or pending isolation', () => {
+ beforeEach(async () => {
+ // Ensure pending action api sets pending action for the test endpoint metadata
+ const pendingActionsResponseProvider = httpMocks.responseProvider.pendingActions.getMockImplementation();
+ httpMocks.responseProvider.pendingActions.mockImplementation((...args) => {
+ const response = pendingActionsResponseProvider!(...args);
+ response.data.some((pendingAction) => {
+ if (pendingAction.agent_id === endpointMeta.elastic.agent.id) {
+ pendingAction.pending_actions.isolate = 1;
+ return true;
+ }
+ return false;
+ });
+ return response;
+ });
+
+ const loadingPendingActions = waitForAction('endpointPendingActionsStateChanged', {
+ validate: (action) => isLoadedResourceState(action.payload),
+ });
+
+ await render({ hostStatus: HostStatus.HEALTHY, endpointMetadata: endpointMeta });
+ await loadingPendingActions;
+ });
+
+ it('should show host pending action', () => {
+ expect(renderResult.getByTestId('rowIsolationStatus').textContent).toEqual(
+ 'Isolating pending'
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx
new file mode 100644
index 0000000000000..94db233972d67
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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, { memo } from 'react';
+import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import styled from 'styled-components';
+import { HostInfo, HostMetadata } from '../../../../../../common/endpoint/types';
+import { HOST_STATUS_TO_BADGE_COLOR } from '../host_constants';
+import { EndpointHostIsolationStatus } from '../../../../../common/components/endpoint/host_isolation';
+import { useEndpointSelector } from '../hooks';
+import { getEndpointHostIsolationStatusPropsCallback } from '../../store/selectors';
+
+const EuiFlexGroupStyled = styled(EuiFlexGroup)`
+ .isolation-status {
+ margin-left: ${({ theme }) => theme.eui.paddingSizes.s};
+ }
+`;
+
+export interface EndpointAgentStatusProps {
+ hostStatus: HostInfo['host_status'];
+ endpointMetadata: HostMetadata;
+}
+export const EndpointAgentStatus = memo(
+ ({ endpointMetadata, hostStatus }) => {
+ const getEndpointIsolationStatusProps = useEndpointSelector(
+ getEndpointHostIsolationStatusPropsCallback
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+);
+
+EndpointAgentStatus.displayName = 'EndpointAgentStatus';
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
index 38404a5c6c11f..64ea575c37d79 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
@@ -23,7 +23,7 @@ import { isPolicyOutOfDate } from '../../utils';
import { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/endpoint/types';
import { useEndpointSelector } from '../hooks';
import { policyResponseStatus, uiQueryParams } from '../../store/selectors';
-import { POLICY_STATUS_TO_BADGE_COLOR, HOST_STATUS_TO_BADGE_COLOR } from '../host_constants';
+import { POLICY_STATUS_TO_BADGE_COLOR } from '../host_constants';
import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { getEndpointDetailsPath } from '../../../../common/routing';
@@ -31,6 +31,7 @@ import { SecurityPageName } from '../../../../../app/types';
import { useFormatUrl } from '../../../../../common/components/link_to';
import { EndpointPolicyLink } from '../components/endpoint_policy_link';
import { OutOfDate } from '../components/out_of_date';
+import { EndpointAgentStatus } from '../components/endpoint_agent_status';
const HostIds = styled(EuiListGroupItem)`
margin-top: 0;
@@ -88,20 +89,7 @@ export const EndpointDetails = memo(
title: i18n.translate('xpack.securitySolution.endpoint.details.agentStatus', {
defaultMessage: 'Agent Status',
}),
- description: (
-
-
-
-
-
- ),
+ description: ,
},
{
title: i18n.translate('xpack.securitySolution.endpoint.details.lastSeen', {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
index 4d1ab0f3de825..410afb4684cd5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
@@ -31,11 +31,7 @@ import { EndpointDetailsFlyout } from './details';
import * as selectors from '../store/selectors';
import { useEndpointSelector } from './hooks';
import { isPolicyOutOfDate } from '../utils';
-import {
- HOST_STATUS_TO_BADGE_COLOR,
- POLICY_STATUS_TO_BADGE_COLOR,
- POLICY_STATUS_TO_TEXT,
-} from './host_constants';
+import { POLICY_STATUS_TO_BADGE_COLOR, POLICY_STATUS_TO_TEXT } from './host_constants';
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { CreateStructuredSelector } from '../../../../common/store';
import { Immutable, HostInfo } from '../../../../../common/endpoint/types';
@@ -59,6 +55,7 @@ import { AdministrationListPage } from '../../../components/administration_list_
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { LinkToApp } from '../../../../common/components/endpoint/link_to_app';
import { TableRowActions } from './components/table_row_actions';
+import { EndpointAgentStatus } from './components/endpoint_agent_status';
const MAX_PAGINATED_ITEM = 9999;
@@ -97,6 +94,7 @@ const EndpointListNavLink = memo<{
});
EndpointListNavLink.displayName = 'EndpointListNavLink';
+// FIXME: this needs refactoring - we are pulling in all selectors from endpoint, which includes many more than what the list uses
const selector = (createStructuredSelector as CreateStructuredSelector)(selectors);
export const EndpointList = () => {
const history = useHistory();
@@ -279,19 +277,9 @@ export const EndpointList = () => {
defaultMessage: 'Agent Status',
}),
// eslint-disable-next-line react/display-name
- render: (hostStatus: HostInfo['host_status']) => {
+ render: (hostStatus: HostInfo['host_status'], endpointInfo) => {
return (
-
-
-
+
);
},
},
From 036c157f10081a39ba0a260f191c37794ed90a2a Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Wed, 16 Jun 2021 16:00:43 +0200
Subject: [PATCH 24/98] [Lens] Unload canvas test properly (#102101)
---
x-pack/test/functional/apps/canvas/lens.ts | 4 ++++
.../feature_controls/index_patterns_security.ts | 1 +
2 files changed, 5 insertions(+)
diff --git a/x-pack/test/functional/apps/canvas/lens.ts b/x-pack/test/functional/apps/canvas/lens.ts
index ed1bf246fae65..67ba40a99684e 100644
--- a/x-pack/test/functional/apps/canvas/lens.ts
+++ b/x-pack/test/functional/apps/canvas/lens.ts
@@ -22,6 +22,10 @@ export default function canvasLensTest({ getService, getPageObjects }: FtrProvid
});
});
+ after(async () => {
+ await esArchiver.unload('x-pack/test/functional/es_archives/canvas/lens');
+ });
+
it('renders lens visualization', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts
index 04f251d247d1b..52fcac769955c 100644
--- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts
+++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts
@@ -20,6 +20,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
describe('security', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana');
+ await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
});
after(async () => {
From a84293b7433d64f4070f886af0c9004e860c0019 Mon Sep 17 00:00:00 2001
From: Kyle Pollich
Date: Wed, 16 Jun 2021 10:26:19 -0400
Subject: [PATCH 25/98] [Fleet + Integrations UI] Address UI Regressions in
Fleet/Integrations (#102250)
* Fix active tabs in integrations UI
Fixes #101771
* Remove duplicate base breadcrumb
Fixes #101785
* Fix i18n
---
.../integrations/hooks/use_breadcrumbs.tsx | 9 +--------
.../sections/epm/screens/home/index.tsx | 18 ++++++++++--------
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
4 files changed, 11 insertions(+), 18 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx
index 5c1745be0c9e4..19f72fdc69bba 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx
@@ -22,14 +22,7 @@ const BASE_BREADCRUMB: ChromeBreadcrumb = {
const breadcrumbGetters: {
[key in Page]?: (values: DynamicPagePathValues) => ChromeBreadcrumb[];
} = {
- integrations: () => [
- BASE_BREADCRUMB,
- {
- text: i18n.translate('xpack.fleet.breadcrumbs.integrationsPageTitle', {
- defaultMessage: 'Integrations',
- }),
- },
- ],
+ integrations: () => [BASE_BREADCRUMB],
integrations_all: () => [
BASE_BREADCRUMB,
{
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx
index 6c635d5d0c9c0..fbd6e07e07bbd 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx
@@ -22,16 +22,18 @@ import { CategoryFacets } from './category_facets';
export const EPMHomePage: React.FC = memo(() => {
return (
-
-
-
+
+
+
-
-
+
+
+
+
-
-
-
+
+
+
);
});
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c67dd383a2ea2..fcb5710f8208f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -8939,7 +8939,6 @@
"xpack.fleet.breadcrumbs.editPackagePolicyPageTitle": "統合の編集",
"xpack.fleet.breadcrumbs.enrollmentTokensPageTitle": "登録トークン",
"xpack.fleet.breadcrumbs.installedIntegrationsPageTitle": "インストール済み",
- "xpack.fleet.breadcrumbs.integrationsPageTitle": "統合",
"xpack.fleet.breadcrumbs.overviewPageTitle": "概要",
"xpack.fleet.breadcrumbs.policiesPageTitle": "ポリシー",
"xpack.fleet.config.invalidPackageVersionError": "有効なサーバーまたはキーワード「latest」でなければなりません",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 23cde5dd1fcff..8c865b62d9d1a 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -9019,7 +9019,6 @@
"xpack.fleet.breadcrumbs.editPackagePolicyPageTitle": "编辑集成",
"xpack.fleet.breadcrumbs.enrollmentTokensPageTitle": "注册令牌",
"xpack.fleet.breadcrumbs.installedIntegrationsPageTitle": "已安装",
- "xpack.fleet.breadcrumbs.integrationsPageTitle": "集成",
"xpack.fleet.breadcrumbs.overviewPageTitle": "概览",
"xpack.fleet.breadcrumbs.policiesPageTitle": "策略",
"xpack.fleet.config.invalidPackageVersionError": "必须是有效的 semver 或关键字 `latest`",
From c4af30845e033250cc13f32f1a143a49a29ec289 Mon Sep 17 00:00:00 2001
From: Pete Hampton
Date: Wed, 16 Jun 2021 15:33:27 +0100
Subject: [PATCH 26/98] Add additional collection item to security allow list
filter. (#102192)
---
.../plugins/security_solution/server/lib/telemetry/sender.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts
index baf4fb2d2cfd0..2b3c002a9b2ae 100644
--- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts
@@ -347,6 +347,9 @@ const allowlistBaseEventFields: AllowlistFields = {
direction: true,
},
registry: {
+ data: {
+ strings: true,
+ },
hive: true,
key: true,
path: true,
From bdc87409ba3c376fb964983f22b2a6405e8828e9 Mon Sep 17 00:00:00 2001
From: Wylie Conlon
Date: Wed, 16 Jun 2021 10:35:55 -0400
Subject: [PATCH 27/98] [Lens] Create mathColumn function to improve
performance (#101908)
* [Lens] Create mathColumn function to improve performance
* Fix empty formula case
* Fix tinymath memoization
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../canvas/canvas-function-reference.asciidoc | 57 +++++++--
packages/kbn-tinymath/src/index.js | 9 +-
.../expression_functions/specs/index.ts | 1 +
.../expression_functions/specs/math_column.ts | 111 ++++++++++++++++++
.../specs/tests/math_column.test.ts | 74 ++++++++++++
.../common/service/expressions_services.ts | 2 +
.../definitions/formula/formula.tsx | 21 +---
.../operations/definitions/formula/math.tsx | 19 +--
8 files changed, 248 insertions(+), 46 deletions(-)
create mode 100644 src/plugins/expressions/common/expression_functions/specs/math_column.ts
create mode 100644 src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts
diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc
index 272cd524c2c20..ac7cbba6e9933 100644
--- a/docs/canvas/canvas-function-reference.asciidoc
+++ b/docs/canvas/canvas-function-reference.asciidoc
@@ -71,7 +71,7 @@ Alias: `condition`
[[alterColumn_fn]]
=== `alterColumn`
-Converts between core types, including `string`, `number`, `null`, `boolean`, and `date`, and renames columns. See also <> and <>.
+Converts between core types, including `string`, `number`, `null`, `boolean`, and `date`, and renames columns. See also <>, <>, and <>.
*Expression syntax*
[source,js]
@@ -1717,11 +1717,16 @@ Adds a column calculated as the result of other columns. Changes are made only w
|===
|Argument |Type |Description
+|`id`
+
+|`string`, `null`
+|An optional id of the resulting column. When no id is provided, the id will be looked up from the existing column by the provided name argument. If no column with this name exists yet, a new column with this name and an identical id will be added to the table.
+
|_Unnamed_ ***
Aliases: `column`, `name`
|`string`
-|The name of the resulting column.
+|The name of the resulting column. Names are not required to be unique.
|`expression` ***
@@ -1729,11 +1734,6 @@ Aliases: `exp`, `fn`, `function`
|`boolean`, `number`, `string`, `null`
|A Canvas expression that is passed to each row as a single row `datatable`.
-|`id`
-
-|`string`, `null`
-|An optional id of the resulting column. When not specified or `null` the name argument is used as id.
-
|`copyMetaFrom`
|`string`, `null`
@@ -1808,6 +1808,47 @@ Default: `"throw"`
*Returns:* `number` | `boolean` | `null`
+[float]
+[[mathColumn_fn]]
+=== `mathColumn`
+
+Adds a column by evaluating `TinyMath` on each row. This function is optimized for math, so it performs better than the <> with a <>.
+*Accepts:* `datatable`
+
+[cols="3*^<"]
+|===
+|Argument |Type |Description
+
+|id ***
+|`string`
+|id of the resulting column. Must be unique.
+
+|name ***
+|`string`
+|The name of the resulting column. Names are not required to be unique.
+
+|_Unnamed_
+
+Alias: `expression`
+|`string`
+|A `TinyMath` expression evaluated on each row. See https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html.
+
+|`onError`
+
+|`string`
+|In case the `TinyMath` evaluation fails or returns NaN, the return value is specified by onError. For example, `"null"`, `"zero"`, `"false"`, `"throw"`. When `"throw"`, it will throw an exception, terminating expression execution.
+
+Default: `"throw"`
+
+|`copyMetaFrom`
+
+|`string`, `null`
+|If set, the meta object from the specified column id is copied over to the specified target column. Throws an exception if the column doesn't exist
+|===
+
+*Returns:* `datatable`
+
+
[float]
[[metric_fn]]
=== `metric`
@@ -2581,7 +2622,7 @@ Default: `false`
[[staticColumn_fn]]
=== `staticColumn`
-Adds a column with the same static value in every row. See also <> and <>.
+Adds a column with the same static value in every row. See also <>, <>, and <>.
*Accepts:* `datatable`
diff --git a/packages/kbn-tinymath/src/index.js b/packages/kbn-tinymath/src/index.js
index 9f1bb7b851463..6fde4c202e2a7 100644
--- a/packages/kbn-tinymath/src/index.js
+++ b/packages/kbn-tinymath/src/index.js
@@ -7,12 +7,11 @@
*/
const { get } = require('lodash');
+const memoizeOne = require('memoize-one');
// eslint-disable-next-line import/no-unresolved
const { parse: parseFn } = require('../grammar');
const { functions: includedFunctions } = require('./functions');
-module.exports = { parse, evaluate, interpret };
-
function parse(input, options) {
if (input == null) {
throw new Error('Missing expression');
@@ -29,9 +28,11 @@ function parse(input, options) {
}
}
+const memoizedParse = memoizeOne(parse);
+
function evaluate(expression, scope = {}, injectedFunctions = {}) {
scope = scope || {};
- return interpret(parse(expression), scope, injectedFunctions);
+ return interpret(memoizedParse(expression), scope, injectedFunctions);
}
function interpret(node, scope, injectedFunctions) {
@@ -79,3 +80,5 @@ function isOperable(args) {
return typeof arg === 'number' && !isNaN(arg);
});
}
+
+module.exports = { parse: memoizedParse, evaluate, interpret };
diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts
index c6d89f41d0e0d..e808021f75180 100644
--- a/src/plugins/expressions/common/expression_functions/specs/index.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/index.ts
@@ -18,3 +18,4 @@ export * from './moving_average';
export * from './ui_setting';
export { mapColumn, MapColumnArguments } from './map_column';
export { math, MathArguments, MathInput } from './math';
+export { mathColumn, MathColumnArguments } from './math_column';
diff --git a/src/plugins/expressions/common/expression_functions/specs/math_column.ts b/src/plugins/expressions/common/expression_functions/specs/math_column.ts
new file mode 100644
index 0000000000000..0ff8faf3ce55a
--- /dev/null
+++ b/src/plugins/expressions/common/expression_functions/specs/math_column.ts
@@ -0,0 +1,111 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { ExpressionFunctionDefinition } from '../types';
+import { math, MathArguments } from './math';
+import { Datatable, DatatableColumn, getType } from '../../expression_types';
+
+export type MathColumnArguments = MathArguments & {
+ id: string;
+ name?: string;
+ copyMetaFrom?: string | null;
+};
+
+export const mathColumn: ExpressionFunctionDefinition<
+ 'mathColumn',
+ Datatable,
+ MathColumnArguments,
+ Datatable
+> = {
+ name: 'mathColumn',
+ type: 'datatable',
+ inputTypes: ['datatable'],
+ help: i18n.translate('expressions.functions.mathColumnHelpText', {
+ defaultMessage:
+ 'Adds a column calculated as the result of other columns. ' +
+ 'Changes are made only when you provide arguments.' +
+ 'See also {alterColumnFn} and {staticColumnFn}.',
+ values: {
+ alterColumnFn: '`alterColumn`',
+ staticColumnFn: '`staticColumn`',
+ },
+ }),
+ args: {
+ ...math.args,
+ id: {
+ types: ['string'],
+ help: i18n.translate('expressions.functions.mathColumn.args.idHelpText', {
+ defaultMessage: 'id of the resulting column. Must be unique.',
+ }),
+ required: true,
+ },
+ name: {
+ types: ['string'],
+ aliases: ['_', 'column'],
+ help: i18n.translate('expressions.functions.mathColumn.args.nameHelpText', {
+ defaultMessage: 'The name of the resulting column. Names are not required to be unique.',
+ }),
+ required: true,
+ },
+ copyMetaFrom: {
+ types: ['string', 'null'],
+ help: i18n.translate('expressions.functions.mathColumn.args.copyMetaFromHelpText', {
+ defaultMessage:
+ "If set, the meta object from the specified column id is copied over to the specified target column. If the column doesn't exist it silently fails.",
+ }),
+ required: false,
+ default: null,
+ },
+ },
+ fn: (input, args, context) => {
+ const columns = [...input.columns];
+ const existingColumnIndex = columns.findIndex(({ id }) => {
+ return id === args.id;
+ });
+ if (existingColumnIndex > -1) {
+ throw new Error('ID must be unique');
+ }
+
+ const newRows = input.rows.map((row) => {
+ return {
+ ...row,
+ [args.id]: math.fn(
+ {
+ type: 'datatable',
+ columns: input.columns,
+ rows: [row],
+ },
+ {
+ expression: args.expression,
+ onError: args.onError,
+ },
+ context
+ ),
+ };
+ });
+ const type = newRows.length ? getType(newRows[0][args.id]) : 'null';
+ const newColumn: DatatableColumn = {
+ id: args.id,
+ name: args.name ?? args.id,
+ meta: { type, params: { id: type } },
+ };
+ if (args.copyMetaFrom) {
+ const metaSourceFrom = columns.find(({ id }) => id === args.copyMetaFrom);
+ newColumn.meta = { ...newColumn.meta, ...(metaSourceFrom?.meta || {}) };
+ }
+
+ columns.push(newColumn);
+
+ return {
+ type: 'datatable',
+ columns,
+ rows: newRows,
+ } as Datatable;
+ },
+};
diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts
new file mode 100644
index 0000000000000..bc6699a2b689b
--- /dev/null
+++ b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts
@@ -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 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 { mathColumn } from '../math_column';
+import { functionWrapper, testTable } from './utils';
+
+describe('mathColumn', () => {
+ const fn = functionWrapper(mathColumn);
+
+ it('throws if the id is used', () => {
+ expect(() => fn(testTable, { id: 'price', name: 'price', expression: 'price * 2' })).toThrow(
+ `ID must be unique`
+ );
+ });
+
+ it('applies math to each row by id', () => {
+ const result = fn(testTable, { id: 'output', name: 'output', expression: 'quantity * price' });
+ expect(result.columns).toEqual([
+ ...testTable.columns,
+ { id: 'output', name: 'output', meta: { params: { id: 'number' }, type: 'number' } },
+ ]);
+ expect(result.rows[0]).toEqual({
+ in_stock: true,
+ name: 'product1',
+ output: 60500,
+ price: 605,
+ quantity: 100,
+ time: 1517842800950,
+ });
+ });
+
+ it('handles onError', () => {
+ const args = {
+ id: 'output',
+ name: 'output',
+ expression: 'quantity / 0',
+ };
+ expect(() => fn(testTable, args)).toThrowError(`Cannot divide by 0`);
+ expect(() => fn(testTable, { ...args, onError: 'throw' })).toThrow();
+ expect(fn(testTable, { ...args, onError: 'zero' }).rows[0].output).toEqual(0);
+ expect(fn(testTable, { ...args, onError: 'false' }).rows[0].output).toEqual(false);
+ expect(fn(testTable, { ...args, onError: 'null' }).rows[0].output).toEqual(null);
+ });
+
+ it('should copy over the meta information from the specified column', async () => {
+ const result = await fn(
+ {
+ ...testTable,
+ columns: [
+ ...testTable.columns,
+ {
+ id: 'myId',
+ name: 'myName',
+ meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } },
+ },
+ ],
+ rows: testTable.rows.map((row) => ({ ...row, myId: Date.now() })),
+ },
+ { id: 'output', name: 'name', copyMetaFrom: 'myId', expression: 'price + 2' }
+ );
+
+ expect(result.type).toBe('datatable');
+ expect(result.columns[result.columns.length - 1]).toEqual({
+ id: 'output',
+ name: 'name',
+ meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } },
+ });
+ });
+});
diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts
index f7afc12aa96ba..b3c0167262661 100644
--- a/src/plugins/expressions/common/service/expressions_services.ts
+++ b/src/plugins/expressions/common/service/expressions_services.ts
@@ -31,6 +31,7 @@ import {
mapColumn,
overallMetric,
math,
+ mathColumn,
} from '../expression_functions';
/**
@@ -344,6 +345,7 @@ export class ExpressionsService implements PersistableStateService
Date: Wed, 16 Jun 2021 11:02:55 -0400
Subject: [PATCH 28/98] [Watcher] Migrate to use new page layout (#101956)
---
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
.../watcher/public/application/app.tsx | 44 +++---
.../components/page_error/page_error.tsx | 2 +-
.../page_error/page_error_forbidden.tsx | 3 +-
.../page_error/page_error_not_exist.tsx | 22 ++-
.../json_watch_edit/json_watch_edit.tsx | 64 +++-----
.../monitoring_watch_edit.tsx | 56 ++-----
.../threshold_watch_edit.tsx | 27 ++--
.../watch_edit/components/watch_edit.tsx | 53 +++----
.../watch_list/components/watch_list.tsx | 149 ++++++++----------
.../watch_status/components/watch_status.tsx | 146 ++++++++---------
.../public/application/shared_imports.ts | 1 +
13 files changed, 262 insertions(+), 309 deletions(-)
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index fcb5710f8208f..fb936a5838781 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -24295,8 +24295,6 @@
"xpack.watcher.sections.watchEdit.json.titlePanel.editWatchTitle": "{watchName}を編集",
"xpack.watcher.sections.watchEdit.loadingWatchDescription": "ウォッチの読み込み中…",
"xpack.watcher.sections.watchEdit.loadingWatchVisualizationDescription": "ウォッチビジュアライゼーションを読み込み中…",
- "xpack.watcher.sections.watchEdit.monitoring.edit.calloutDescriptionText": "ウォッチ'{watchName}'はシステムウォッチであるため、編集できません。{watchStatusLink}",
- "xpack.watcher.sections.watchEdit.monitoring.edit.calloutTitleText": "このウォッチは編集できません。",
"xpack.watcher.sections.watchEdit.monitoring.header.watchLinkTitle": "ウォッチステータスを表示します。",
"xpack.watcher.sections.watchEdit.simulate.form.actionModesFieldLabel": "アクションモード",
"xpack.watcher.sections.watchEdit.simulate.form.actionOverridesDescription": "ウォッチでアクションを実行またはスキップすることができるようにします。{actionsLink}",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 8c865b62d9d1a..998b2a4c67287 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -24665,8 +24665,6 @@
"xpack.watcher.sections.watchEdit.json.titlePanel.editWatchTitle": "编辑 {watchName}",
"xpack.watcher.sections.watchEdit.loadingWatchDescription": "正在加载监视……",
"xpack.watcher.sections.watchEdit.loadingWatchVisualizationDescription": "正在加载监视可视化……",
- "xpack.watcher.sections.watchEdit.monitoring.edit.calloutDescriptionText": "监视“{watchName}”为系统监视,无法编辑。{watchStatusLink}",
- "xpack.watcher.sections.watchEdit.monitoring.edit.calloutTitleText": "此监视无法编辑。",
"xpack.watcher.sections.watchEdit.monitoring.header.watchLinkTitle": "查看监视状态。",
"xpack.watcher.sections.watchEdit.simulate.form.actionModesFieldLabel": "操作模式",
"xpack.watcher.sections.watchEdit.simulate.form.actionOverridesDescription": "允许监视执行或跳过操作。{actionsLink}",
diff --git a/x-pack/plugins/watcher/public/application/app.tsx b/x-pack/plugins/watcher/public/application/app.tsx
index 6c233a44830b5..98d10bce3b6b2 100644
--- a/x-pack/plugins/watcher/public/application/app.tsx
+++ b/x-pack/plugins/watcher/public/application/app.tsx
@@ -17,7 +17,7 @@ import {
import { Router, Switch, Route, Redirect, withRouter, RouteComponentProps } from 'react-router-dom';
-import { EuiCallOut, EuiLink } from '@elastic/eui';
+import { EuiPageContent, EuiEmptyPrompt, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -62,24 +62,30 @@ export const App = (deps: AppDeps) => {
if (!valid) {
return (
-
- }
- color="danger"
- iconType="help"
- >
- {message}{' '}
-
-
-
-
+
+
+
+
+ }
+ body={{message}
}
+ actions={[
+
+
+ ,
+ ]}
+ />
+
);
}
return (
diff --git a/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx b/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx
index ca05d390518f2..321b5c0e5e11b 100644
--- a/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx
+++ b/x-pack/plugins/watcher/public/application/components/page_error/page_error.tsx
@@ -25,7 +25,7 @@ export function getPageErrorCode(errorOrErrors: any) {
}
}
-export function PageError({ errorCode, id }: { errorCode?: any; id?: any }) {
+export function PageError({ errorCode, id }: { errorCode?: number; id?: string }) {
switch (errorCode) {
case 404:
return ;
diff --git a/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx b/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx
index c2e93c7f06600..56dc5c7dc22b5 100644
--- a/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx
+++ b/x-pack/plugins/watcher/public/application/components/page_error/page_error_forbidden.tsx
@@ -13,8 +13,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
export function PageErrorForbidden() {
return (
-
+ {id ? (
+
+ ) : (
+
+ )}
}
/>
diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx
index 8b5827fbd0fe0..80931c3f60c05 100644
--- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx
+++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx
@@ -7,15 +7,7 @@
import React, { useContext, useState } from 'react';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiPageContent,
- EuiSpacer,
- EuiTab,
- EuiTabs,
- EuiTitle,
-} from '@elastic/eui';
+import { EuiPageHeader, EuiSpacer, EuiPageContentBody } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ExecuteDetails } from '../../../../models/execute_details';
import { getActionType } from '../../../../../../common/lib/get_action_type';
@@ -96,36 +88,31 @@ export const JsonWatchEdit = ({ pageTitle }: { pageTitle: string }) => {
const hasExecuteWatchErrors = !!Object.keys(executeWatchErrors).find(
(errorKey) => executeWatchErrors[errorKey].length >= 1
);
+
return (
-
-
-
-
- {pageTitle}
-
-
-
-
- {WATCH_TABS.map((tab, index) => (
- {
- setSelectedTab(tab.id);
- setExecuteDetails(
- new ExecuteDetails({
- ...executeDetails,
- actionModes: getActionModes(watchActions),
- })
- );
- }}
- isSelected={tab.id === selectedTab}
- key={index}
- data-test-subj="tab"
- >
- {tab.name}
-
- ))}
-
+
+ {pageTitle}}
+ bottomBorder
+ tabs={WATCH_TABS.map((tab, index) => ({
+ onClick: () => {
+ setSelectedTab(tab.id);
+ setExecuteDetails(
+ new ExecuteDetails({
+ ...executeDetails,
+ actionModes: getActionModes(watchActions),
+ })
+ );
+ },
+ isSelected: tab.id === selectedTab,
+ key: index,
+ 'data-test-subj': 'tab',
+ label: tab.name,
+ }))}
+ />
+
+
{selectedTab === WATCH_SIMULATE_TAB && (
{
watchActions={watchActions}
/>
)}
+
{selectedTab === WATCH_EDIT_TAB && }
-
+
);
};
diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx
index 930c11340ce5e..b00e4dc310e27 100644
--- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx
+++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx
@@ -7,16 +7,7 @@
import React, { useContext } from 'react';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiPageContent,
- EuiSpacer,
- EuiTitle,
- EuiCallOut,
- EuiText,
- EuiLink,
-} from '@elastic/eui';
+import { EuiPageContent, EuiEmptyPrompt, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { WatchContext } from '../../watch_context';
import { useAppContext } from '../../../../app_context';
@@ -27,46 +18,31 @@ export const MonitoringWatchEdit = ({ pageTitle }: { pageTitle: string }) => {
const { watch } = useContext(WatchContext);
const { history } = useAppContext();
- const systemWatchTitle = (
-
- );
-
const systemWatchMessage = (
-
-
- ),
}}
/>
);
return (
-
-
-
-
- {pageTitle}
-
-
-
-
-
-
- {systemWatchMessage}
-
-
+
+ {pageTitle}}
+ body={{systemWatchMessage}
}
+ actions={[
+
+
+ ,
+ ]}
+ />
);
};
diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx
index 2f89a3bc2be64..6587974363a80 100644
--- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx
+++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx
@@ -18,13 +18,14 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiForm,
- EuiPageContent,
EuiPopover,
EuiPopoverTitle,
EuiSelect,
EuiSpacer,
EuiText,
EuiTitle,
+ EuiPageHeader,
+ EuiPageContentBody,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -236,19 +237,15 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => {
};
return (
-
-
-
-
- {pageTitle}
-
-
-
- {watch.titleDescription}
-
-
-
-
+
+ {pageTitle}}
+ description={watch.titleDescription}
+ bottomBorder
+ />
+
+
+
{serverError && (
@@ -957,6 +954,6 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => {
close={() => setIsRequestVisible(false)}
/>
) : null}
-
+
);
};
diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx
index 525ae077df655..fa3c7e374f7b5 100644
--- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx
+++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/watch_edit.tsx
@@ -10,19 +10,20 @@ import { isEqual } from 'lodash';
import { EuiPageContent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-
import { FormattedMessage } from '@kbn/i18n/react';
-import { Watch } from '../../../models/watch';
+
import { WATCH_TYPES } from '../../../../../common/constants';
import { BaseWatch } from '../../../../../common/types/watch_types';
-import { getPageErrorCode, PageError, SectionLoading, SectionError } from '../../../components';
+import { getPageErrorCode, PageError, SectionLoading } from '../../../components';
import { loadWatch } from '../../../lib/api';
import { listBreadcrumb, editBreadcrumb, createBreadcrumb } from '../../../lib/breadcrumbs';
+import { useAppContext } from '../../../app_context';
+import { Watch } from '../../../models/watch';
+import { PageError as GenericPageError } from '../../../shared_imports';
+import { WatchContext } from '../watch_context';
import { JsonWatchEdit } from './json_watch_edit';
import { ThresholdWatchEdit } from './threshold_watch_edit';
import { MonitoringWatchEdit } from './monitoring_watch_edit';
-import { WatchContext } from '../watch_context';
-import { useAppContext } from '../../../app_context';
const getTitle = (watch: BaseWatch) => {
if (watch.isNew) {
@@ -115,7 +116,7 @@ export const WatchEdit = ({
const loadedWatch = await loadWatch(id);
dispatch({ command: 'setWatch', payload: loadedWatch });
} catch (error) {
- dispatch({ command: 'setError', payload: error });
+ dispatch({ command: 'setError', payload: error.body });
}
} else if (type) {
const WatchType = Watch.getWatchTypes()[type];
@@ -135,36 +136,34 @@ export const WatchEdit = ({
const errorCode = getPageErrorCode(loadError);
if (errorCode) {
return (
-
+
);
- }
-
- if (loadError) {
+ } else if (loadError) {
return (
-
-
- }
- error={loadError}
- />
-
+
+ }
+ error={loadError}
+ />
);
}
if (!watch) {
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx
index 0e89871063507..31accef0b6369 100644
--- a/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx
+++ b/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx
@@ -11,25 +11,25 @@ import {
CriteriaWithPagination,
EuiButton,
EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
EuiInMemoryTable,
EuiLink,
EuiPageContent,
EuiSpacer,
EuiText,
- EuiTitle,
EuiToolTip,
EuiEmptyPrompt,
EuiButtonIcon,
EuiPopover,
EuiContextMenuPanel,
EuiContextMenuItem,
+ EuiPageHeader,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { Moment } from 'moment';
+import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
+
import { REFRESH_INTERVALS, PAGINATION, WATCH_TYPES } from '../../../../../common/constants';
import { listBreadcrumb } from '../../../lib/breadcrumbs';
import {
@@ -37,15 +37,13 @@ import {
PageError,
DeleteWatchesModal,
WatchStatus,
- SectionError,
SectionLoading,
Error,
} from '../../../components';
import { useLoadWatches } from '../../../lib/api';
import { goToCreateThresholdAlert, goToCreateAdvancedWatch } from '../../../lib/navigation';
import { useAppContext } from '../../../app_context';
-
-import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
+import { PageError as GenericPageError } from '../../../shared_imports';
export const WatchList = () => {
// hooks
@@ -173,21 +171,36 @@ export const WatchList = () => {
if (isWatchesLoading) {
return (
-
-
-
+
+
+
+
+
);
}
- if (getPageErrorCode(error)) {
+ const errorCode = getPageErrorCode(error);
+ if (errorCode) {
return (
-
-
+
+
);
+ } else if (error) {
+ return (
+
+ }
+ error={(error as unknown) as Error}
+ />
+ );
}
if (availableWatches && availableWatches.length === 0) {
@@ -206,7 +219,7 @@ export const WatchList = () => {
);
return (
-
+
{
let content;
- if (error) {
- content = (
-
- }
- error={(error as unknown) as Error}
- />
- );
- } else if (availableWatches) {
+ if (availableWatches) {
const columns = [
{
field: 'id',
@@ -463,56 +464,46 @@ export const WatchList = () => {
);
}
- if (content) {
- return (
-
- {
- if (deleted) {
- setDeletedWatches([...deletedWatches, ...watchesToDelete]);
- }
- setWatchesToDelete([]);
- }}
- watchesToDelete={watchesToDelete}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {watcherDescriptionText}
-
+ return (
+ <>
+
+
+
+ }
+ bottomBorder
+ rightSideItems={[
+
+
+ ,
+ ]}
+ description={watcherDescriptionText}
+ />
+ {
+ if (deleted) {
+ setDeletedWatches([...deletedWatches, ...watchesToDelete]);
+ }
+ setWatchesToDelete([]);
+ }}
+ watchesToDelete={watchesToDelete}
+ />
-
+
- {content}
-
- );
- }
- return null;
+ {content}
+ >
+ );
};
diff --git a/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx b/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx
index 1e3548620339a..73400b9ccaaa7 100644
--- a/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx
+++ b/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_status.tsx
@@ -9,14 +9,10 @@ import React, { useEffect, useState } from 'react';
import {
EuiPageContent,
EuiSpacer,
- EuiTabs,
- EuiTab,
- EuiFlexGroup,
- EuiFlexItem,
- EuiTitle,
EuiToolTip,
EuiBadge,
EuiButtonEmpty,
+ EuiPageHeader,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -88,18 +84,20 @@ export const WatchStatus = ({
if (isWatchDetailLoading) {
return (
-
-
-
+
+
+
+
+
);
}
if (errorCode) {
return (
-
+
);
@@ -156,20 +154,11 @@ export const WatchStatus = ({
return (
-
- {
- if (deleted) {
- goToWatchList();
- }
- setWatchesToDelete([]);
- }}
- watchesToDelete={watchesToDelete}
- />
-
-
-
-
+ <>
+
+
-
-
-
- {isSystemWatch ? (
-
-
- }
- >
-
-
-
-
-
- ) : (
-
-
-
+
+ {isSystemWatch && (
+ <>
+ {' '}
+
+ }
+ >
+
+
+
+
+ >
+ )}
+ >
+ }
+ bottomBorder
+ rightSideItems={
+ isSystemWatch
+ ? []
+ : [
toggleWatchActivation()}
isLoading={isTogglingActivation}
>
{activationButtonText}
-
-
-
+ ,
{
@@ -223,30 +213,34 @@ export const WatchStatus = ({
id="xpack.watcher.sections.watchHistory.deleteWatchButtonLabel"
defaultMessage="Delete"
/>
-
-
-
-
- )}
-
-
-
- {WATCH_STATUS_TABS.map((tab, index) => (
- {
- setSelectedTab(tab.id);
- }}
- isSelected={tab.id === selectedTab}
- key={index}
- data-test-subj="tab"
- >
- {tab.name}
-
- ))}
-
+ ,
+ ]
+ }
+ tabs={WATCH_STATUS_TABS.map((tab, index) => ({
+ onClick: () => {
+ setSelectedTab(tab.id);
+ },
+ isSelected: tab.id === selectedTab,
+ key: index,
+ 'data-test-subj': 'tab',
+ label: tab.name,
+ }))}
+ />
+
+
{selectedTab === WATCH_ACTIONS_TAB ? : }
-
+
+ {
+ if (deleted) {
+ goToWatchList();
+ }
+ setWatchesToDelete([]);
+ }}
+ watchesToDelete={watchesToDelete}
+ />
+ >
);
}
diff --git a/x-pack/plugins/watcher/public/application/shared_imports.ts b/x-pack/plugins/watcher/public/application/shared_imports.ts
index e3eb11eda77b3..44bef3b0c4f5f 100644
--- a/x-pack/plugins/watcher/public/application/shared_imports.ts
+++ b/x-pack/plugins/watcher/public/application/shared_imports.ts
@@ -12,4 +12,5 @@ export {
sendRequest,
useRequest,
XJson,
+ PageError,
} from '../../../../../src/plugins/es_ui_shared/public';
From 8abb656d7f929c2a93b143b9e87f6ddc00d1f3a2 Mon Sep 17 00:00:00 2001
From: Liza Katz
Date: Wed, 16 Jun 2021 18:15:47 +0300
Subject: [PATCH 29/98] [Kuery] Move json utils (#102058)
* Move JSON utils to utils package
* Imports from tests
* delete
* split package
* docs
* test
* test
* imports
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
...bana-plugin-plugins-data-public.eskuery.md | 2 +-
...bana-plugin-plugins-data-server.eskuery.md | 2 +-
package.json | 1 +
packages/BUILD.bazel | 1 +
packages/kbn-common-utils/BUILD.bazel | 82 +++++++++++++++++++
packages/kbn-common-utils/README.md | 3 +
packages/kbn-common-utils/jest.config.js | 13 +++
packages/kbn-common-utils/package.json | 9 ++
packages/kbn-common-utils/src/index.ts | 9 ++
packages/kbn-common-utils/src/json/index.ts | 9 ++
.../kbn-common-utils/src/json}/typed_json.ts | 0
packages/kbn-common-utils/tsconfig.json | 18 ++++
.../data/common/es_query/kuery/ast/ast.ts | 2 +-
.../es_query/kuery/node_types/named_arg.ts | 2 +-
.../common/es_query/kuery/node_types/types.ts | 2 +-
src/plugins/data/public/public.api.md | 3 +-
src/plugins/data/server/server.api.md | 3 +-
src/plugins/kibana_utils/common/index.ts | 1 -
src/plugins/kibana_utils/public/index.ts | 3 -
.../alerting/common/alert_navigation.ts | 3 +-
.../public/alert_navigation_registry/types.ts | 2 +-
.../authorization/alerting_authorization.ts | 2 +-
.../alerting_authorization_kuery.ts | 2 +-
.../server/routes/lib/rewrite_request_case.ts | 2 +-
.../graph/public/types/workspace_state.ts | 2 +-
x-pack/plugins/infra/common/typed_json.ts | 2 +-
.../components/log_stream/log_stream.tsx | 2 +-
.../logging/log_text_stream/field_value.tsx | 2 +-
.../log_entry_field_column.tsx | 2 +-
.../utils/log_column_render_configuration.tsx | 2 +-
.../lib/adapters/framework/adapter_types.ts | 2 +-
.../log_entries/kibana_log_entries_adapter.ts | 2 +-
.../log_entries_domain/log_entries_domain.ts | 3 +-
.../snapshot/lib/get_metrics_aggregations.ts | 2 +-
.../services/log_entries/message/message.ts | 2 +-
.../log_entries/message/rule_types.ts | 2 +-
.../infra/server/utils/serialized_query.ts | 2 +-
.../server/utils/typed_search_strategy.ts | 2 +-
x-pack/plugins/ml/common/types/es_client.ts | 2 +-
x-pack/plugins/osquery/common/typed_json.ts | 2 +-
.../security_solution/common/typed_json.ts | 2 +-
.../public/common/lib/keury/index.ts | 2 +-
.../routes/resolver/queries/events.ts | 2 +-
.../resolver/tree/queries/descendants.ts | 2 +-
.../routes/resolver/tree/queries/lifecycle.ts | 2 +-
.../routes/resolver/tree/queries/stats.ts | 2 +-
.../routes/resolver/utils/pagination.ts | 2 +-
.../server/endpoint/types.ts | 2 +-
.../server/utils/serialized_query.ts | 2 +-
.../server/monitoring/capacity_estimation.ts | 2 +-
.../monitoring_stats_stream.test.ts | 2 +-
.../monitoring/monitoring_stats_stream.ts | 2 +-
.../runtime_statistics_aggregator.ts | 2 +-
.../server/monitoring/task_run_calcultors.ts | 2 +-
.../server/monitoring/task_run_statistics.ts | 2 +-
.../server/monitoring/workload_statistics.ts | 2 +-
.../task_manager/server/routes/health.ts | 2 +-
.../uptime/server/lib/alerts/status_check.ts | 2 +-
.../server/lib/requests/get_monitor_status.ts | 2 +-
.../tests/services/annotations.ts | 2 +-
.../basic/tests/annotations.ts | 2 +-
.../trial/tests/annotations.ts | 2 +-
.../apis/resolver/events.ts | 2 +-
yarn.lock | 4 +
64 files changed, 203 insertions(+), 56 deletions(-)
create mode 100644 packages/kbn-common-utils/BUILD.bazel
create mode 100644 packages/kbn-common-utils/README.md
create mode 100644 packages/kbn-common-utils/jest.config.js
create mode 100644 packages/kbn-common-utils/package.json
create mode 100644 packages/kbn-common-utils/src/index.ts
create mode 100644 packages/kbn-common-utils/src/json/index.ts
rename {src/plugins/kibana_utils/common => packages/kbn-common-utils/src/json}/typed_json.ts (100%)
create mode 100644 packages/kbn-common-utils/tsconfig.json
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md
index 5d92e348d6276..2cde2b7455585 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md
@@ -10,6 +10,6 @@
esKuery: {
nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes;
fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode;
- toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject;
+ toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
}
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md
index 19cb742785e7b..4b96d8af756f3 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md
@@ -10,6 +10,6 @@
esKuery: {
nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes;
fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode;
- toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject;
+ toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
}
```
diff --git a/package.json b/package.json
index c9c6fa7f582c5..596bcff59797d 100644
--- a/package.json
+++ b/package.json
@@ -156,6 +156,7 @@
"@kbn/ui-framework": "link:packages/kbn-ui-framework",
"@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps",
"@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types",
+ "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils",
"@kbn/utils": "link:bazel-bin/packages/kbn-utils",
"@loaders.gl/core": "^2.3.1",
"@loaders.gl/json": "^2.3.1",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index 3e17d471a3cac..f2510a2386aa2 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -12,6 +12,7 @@ filegroup(
"//packages/kbn-apm-utils:build",
"//packages/kbn-babel-code-parser:build",
"//packages/kbn-babel-preset:build",
+ "//packages/kbn-common-utils:build",
"//packages/kbn-config:build",
"//packages/kbn-config-schema:build",
"//packages/kbn-crypto:build",
diff --git a/packages/kbn-common-utils/BUILD.bazel b/packages/kbn-common-utils/BUILD.bazel
new file mode 100644
index 0000000000000..0244684973353
--- /dev/null
+++ b/packages/kbn-common-utils/BUILD.bazel
@@ -0,0 +1,82 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-common-utils"
+PKG_REQUIRE_NAME = "@kbn/common-utils"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ ],
+ exclude = ["**/*.test.*"],
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "package.json",
+ "README.md"
+]
+
+SRC_DEPS = [
+ "//packages/kbn-config-schema",
+ "@npm//load-json-file",
+ "@npm//tslib",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/jest",
+ "@npm//@types/node",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":tsc"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-common-utils/README.md b/packages/kbn-common-utils/README.md
new file mode 100644
index 0000000000000..7b64c9f18fe89
--- /dev/null
+++ b/packages/kbn-common-utils/README.md
@@ -0,0 +1,3 @@
+# @kbn/common-utils
+
+Shared common (client and server sie) utilities shared across packages and plugins.
\ No newline at end of file
diff --git a/packages/kbn-common-utils/jest.config.js b/packages/kbn-common-utils/jest.config.js
new file mode 100644
index 0000000000000..08f1995c47423
--- /dev/null
+++ b/packages/kbn-common-utils/jest.config.js
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+module.exports = {
+ preset: '@kbn/test',
+ rootDir: '../..',
+ roots: ['/packages/kbn-common-utils'],
+};
diff --git a/packages/kbn-common-utils/package.json b/packages/kbn-common-utils/package.json
new file mode 100644
index 0000000000000..db99f4d6afb98
--- /dev/null
+++ b/packages/kbn-common-utils/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@kbn/common-utils",
+ "main": "./target/index.js",
+ "browser": "./target/index.js",
+ "types": "./target/index.d.ts",
+ "version": "1.0.0",
+ "license": "SSPL-1.0 OR Elastic License 2.0",
+ "private": true
+}
\ No newline at end of file
diff --git a/packages/kbn-common-utils/src/index.ts b/packages/kbn-common-utils/src/index.ts
new file mode 100644
index 0000000000000..1b8bffe4bf158
--- /dev/null
+++ b/packages/kbn-common-utils/src/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export * from './json';
diff --git a/packages/kbn-common-utils/src/json/index.ts b/packages/kbn-common-utils/src/json/index.ts
new file mode 100644
index 0000000000000..96c94df1bb48e
--- /dev/null
+++ b/packages/kbn-common-utils/src/json/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { JsonArray, JsonValue, JsonObject } from './typed_json';
diff --git a/src/plugins/kibana_utils/common/typed_json.ts b/packages/kbn-common-utils/src/json/typed_json.ts
similarity index 100%
rename from src/plugins/kibana_utils/common/typed_json.ts
rename to packages/kbn-common-utils/src/json/typed_json.ts
diff --git a/packages/kbn-common-utils/tsconfig.json b/packages/kbn-common-utils/tsconfig.json
new file mode 100644
index 0000000000000..98f1b30c0d7ff
--- /dev/null
+++ b/packages/kbn-common-utils/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "incremental": true,
+ "outDir": "target",
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "sourceRoot": "../../../../packages/kbn-common-utils/src",
+ "types": [
+ "jest",
+ "node"
+ ]
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.ts b/src/plugins/data/common/es_query/kuery/ast/ast.ts
index 5b22e3b3a3e0e..be82128969968 100644
--- a/src/plugins/data/common/es_query/kuery/ast/ast.ts
+++ b/src/plugins/data/common/es_query/kuery/ast/ast.ts
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { JsonObject } from '@kbn/common-utils';
import { nodeTypes } from '../node_types/index';
import { KQLSyntaxError } from '../kuery_syntax_error';
import { KueryNode, DslQuery, KueryParseOptions } from '../types';
@@ -13,7 +14,6 @@ import { IIndexPattern } from '../../../index_patterns/types';
// @ts-ignore
import { parse as parseKuery } from './_generated_/kuery';
-import { JsonObject } from '../../../../../kibana_utils/common';
const fromExpression = (
expression: string | DslQuery,
diff --git a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts
index c65f195040b18..b1b202e4323af 100644
--- a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts
+++ b/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts
@@ -7,10 +7,10 @@
*/
import _ from 'lodash';
+import { JsonObject } from '@kbn/common-utils';
import * as ast from '../ast';
import { nodeTypes } from '../node_types';
import { NamedArgTypeBuildNode } from './types';
-import { JsonObject } from '../../../../../kibana_utils/common';
export function buildNode(name: string, value: any): NamedArgTypeBuildNode {
const argumentNode =
diff --git a/src/plugins/data/common/es_query/kuery/node_types/types.ts b/src/plugins/data/common/es_query/kuery/node_types/types.ts
index 196890ed0f7a3..b3247a0ad8dc2 100644
--- a/src/plugins/data/common/es_query/kuery/node_types/types.ts
+++ b/src/plugins/data/common/es_query/kuery/node_types/types.ts
@@ -10,8 +10,8 @@
* WARNING: these typings are incomplete
*/
+import { JsonValue } from '@kbn/common-utils';
import { IIndexPattern } from '../../../index_patterns';
-import { JsonValue } from '../../../../../kibana_utils/common';
import { KueryNode } from '..';
export type FunctionName =
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 13352d183370b..d56727b468da6 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -53,6 +53,7 @@ import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public';
import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public';
import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { IUiSettingsClient } from 'src/core/public';
+import { JsonValue } from '@kbn/common-utils';
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { Location } from 'history';
import { LocationDescriptorObject } from 'history';
@@ -856,7 +857,7 @@ export const esFilters: {
export const esKuery: {
nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes;
fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode;
- toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject;
+ toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
};
// Warning: (ae-missing-release-tag) "esQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 783bd8d2fcd0e..c2b533bc42dc6 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -38,6 +38,7 @@ import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public';
import { ISearchSource } from 'src/plugins/data/public';
import { IUiSettingsClient } from 'src/core/server';
import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/server';
+import { JsonValue } from '@kbn/common-utils';
import { KibanaRequest } from 'src/core/server';
import { KibanaRequest as KibanaRequest_2 } from 'kibana/server';
import { Logger } from 'src/core/server';
@@ -460,7 +461,7 @@ export const esFilters: {
export const esKuery: {
nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes;
fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode;
- toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("../../kibana_utils/common").JsonObject;
+ toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IIndexPattern | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject;
};
// Warning: (ae-missing-release-tag) "esQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts
index 76a7cb2855c6e..773c0b96d6413 100644
--- a/src/plugins/kibana_utils/common/index.ts
+++ b/src/plugins/kibana_utils/common/index.ts
@@ -11,7 +11,6 @@ export * from './field_wildcard';
export * from './of';
export * from './ui';
export * from './state_containers';
-export * from './typed_json';
export * from './errors';
export { AbortError, abortSignalToPromise } from './abort_utils';
export { createGetterSetter, Get, Set } from './create_getter_setter';
diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts
index 75c52e1301ea5..3d9b5db062955 100644
--- a/src/plugins/kibana_utils/public/index.ts
+++ b/src/plugins/kibana_utils/public/index.ts
@@ -15,9 +15,6 @@ export {
fieldWildcardFilter,
fieldWildcardMatcher,
Get,
- JsonArray,
- JsonObject,
- JsonValue,
of,
Set,
UiComponent,
diff --git a/x-pack/plugins/alerting/common/alert_navigation.ts b/x-pack/plugins/alerting/common/alert_navigation.ts
index d26afff9e8243..7c9e428f9a09e 100644
--- a/x-pack/plugins/alerting/common/alert_navigation.ts
+++ b/x-pack/plugins/alerting/common/alert_navigation.ts
@@ -5,8 +5,7 @@
* 2.0.
*/
-import { JsonObject } from '../../../../src/plugins/kibana_utils/common';
-
+import { JsonObject } from '@kbn/common-utils';
export interface AlertUrlNavigation {
path: string;
}
diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts
index 53540facd9652..12ac906142647 100644
--- a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts
+++ b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { SanitizedAlert } from '../../common';
/**
diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts
index 7506accd8b88e..52cef9a402e35 100644
--- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts
+++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts
@@ -8,6 +8,7 @@
import Boom from '@hapi/boom';
import { map, mapValues, fromPairs, has } from 'lodash';
import { KibanaRequest } from 'src/core/server';
+import { JsonObject } from '@kbn/common-utils';
import { AlertTypeRegistry } from '../types';
import { SecurityPluginSetup } from '../../../security/server';
import { RegistryAlertType } from '../alert_type_registry';
@@ -19,7 +20,6 @@ import {
AlertingAuthorizationFilterOpts,
} from './alerting_authorization_kuery';
import { KueryNode } from '../../../../../src/plugins/data/server';
-import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
export enum AlertingAuthorizationEntity {
Rule = 'rule',
diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts
index eb6f1605f2ba5..5205e6afdf29f 100644
--- a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts
+++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts
@@ -6,7 +6,7 @@
*/
import { remove } from 'lodash';
-import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { nodeBuilder, EsQueryConfig } from '../../../../../src/plugins/data/common';
import { toElasticsearchQuery } from '../../../../../src/plugins/data/common/es_query';
import { KueryNode } from '../../../../../src/plugins/data/server';
diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts
index 361ba5ff5e55d..f5455d1a63093 100644
--- a/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts
+++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { JsonValue } from '../../../../../../src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
type RenameAlertToRule = K extends `alertTypeId`
? `ruleTypeId`
diff --git a/x-pack/plugins/graph/public/types/workspace_state.ts b/x-pack/plugins/graph/public/types/workspace_state.ts
index 73d76cfd9cc57..e511a2eb5c779 100644
--- a/x-pack/plugins/graph/public/types/workspace_state.ts
+++ b/x-pack/plugins/graph/public/types/workspace_state.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
+import { JsonObject } from '@kbn/common-utils';
import { FontawesomeIcon } from '../helpers/style_choices';
import { WorkspaceField, AdvancedSettings } from './app_state';
-import { JsonObject } from '../../../../../src/plugins/kibana_utils/public';
export interface WorkspaceNode {
x: number;
diff --git a/x-pack/plugins/infra/common/typed_json.ts b/x-pack/plugins/infra/common/typed_json.ts
index 5b7bbdcfbc07b..44409ab433a60 100644
--- a/x-pack/plugins/infra/common/typed_json.ts
+++ b/x-pack/plugins/infra/common/typed_json.ts
@@ -6,7 +6,7 @@
*/
import * as rt from 'io-ts';
-import { JsonArray, JsonObject, JsonValue } from '../../../../src/plugins/kibana_utils/common';
+import { JsonArray, JsonObject, JsonValue } from '@kbn/common-utils';
export { JsonArray, JsonObject, JsonValue };
diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx
index 44d78591fbf2f..0087d559a42e6 100644
--- a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx
+++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx
@@ -7,6 +7,7 @@
import React, { useMemo, useCallback, useEffect } from 'react';
import { noop } from 'lodash';
+import { JsonValue } from '@kbn/common-utils';
import { DataPublicPluginStart, esQuery, Filter } from '../../../../../../src/plugins/data/public';
import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
import { LogEntryCursor } from '../../../common/log_entry';
@@ -17,7 +18,6 @@ import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream';
import { ScrollableLogTextStreamView } from '../logging/log_text_stream';
import { LogColumnRenderConfiguration } from '../../utils/log_column_render_configuration';
-import { JsonValue } from '../../../../../../src/plugins/kibana_utils/common';
import { Query } from '../../../../../../src/plugins/data/common';
import { LogStreamErrorBoundary } from './log_stream_error_boundary';
diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx
index 29e511b2467e1..9cffef270219e 100644
--- a/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx
@@ -7,8 +7,8 @@
import stringify from 'json-stable-stringify';
import React from 'react';
+import { JsonArray, JsonValue } from '@kbn/common-utils';
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
-import { JsonArray, JsonValue } from '../../../../../../../src/plugins/kibana_utils/common';
import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting';
export const FieldValue: React.FC<{
diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx
index 4fffa8eb0ee02..33e81756552d8 100644
--- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { JsonValue } from '../../../../../../../src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
import { LogColumn } from '../../../../common/log_entry';
import { isFieldColumn, isHighlightFieldColumn } from '../../../utils/log_entry';
diff --git a/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx b/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx
index 3758e02e77312..a6adc716e02fb 100644
--- a/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx
+++ b/x-pack/plugins/infra/public/utils/log_column_render_configuration.tsx
@@ -6,7 +6,7 @@
*/
import { ReactNode } from 'react';
-import { JsonValue } from '../../../../../src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
/**
* Interface for common configuration properties, regardless of the column type.
diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
index 1231a19f80ca2..1657d41d0b793 100644
--- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
+++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
@@ -8,6 +8,7 @@
import { GenericParams, SearchResponse } from 'elasticsearch';
import { Lifecycle } from '@hapi/hapi';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { JsonArray, JsonValue } from '@kbn/common-utils';
import { RouteConfig, RouteMethod } from '../../../../../../../src/core/server';
import {
PluginSetup as DataPluginSetup,
@@ -19,7 +20,6 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../pl
import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server';
import { PluginSetupContract as AlertingPluginContract } from '../../../../../alerting/server';
import { MlPluginSetup } from '../../../../../ml/server';
-import { JsonArray, JsonValue } from '../../../../../../../src/plugins/kibana_utils/common';
export interface InfraServerPluginSetupDeps {
data: DataPluginSetup;
diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts
index 3aaa747b945a8..9f2e9e2713bbc 100644
--- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts
+++ b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts
@@ -11,7 +11,7 @@ import { constant, identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import * as runtimeTypes from 'io-ts';
import { compact } from 'lodash';
-import { JsonArray } from '../../../../../../../src/plugins/kibana_utils/common';
+import { JsonArray } from '@kbn/common-utils';
import type { InfraPluginRequestHandlerContext } from '../../../types';
import {
LogEntriesAdapter,
diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts
index ad8650bbb0fb6..f8268570710f2 100644
--- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts
+++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts
@@ -6,7 +6,8 @@
*/
import type { estypes } from '@elastic/elasticsearch';
-import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
+
import type { InfraPluginRequestHandlerContext } from '../../../types';
import {
diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts
index 0878131a69f0e..33060f428b7ff 100644
--- a/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts
+++ b/x-pack/plugins/infra/server/routes/snapshot/lib/get_metrics_aggregations.ts
@@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import {
InventoryItemType,
MetricsUIAggregation,
diff --git a/x-pack/plugins/infra/server/services/log_entries/message/message.ts b/x-pack/plugins/infra/server/services/log_entries/message/message.ts
index 83d9aa6c8954c..2deee584f5187 100644
--- a/x-pack/plugins/infra/server/services/log_entries/message/message.ts
+++ b/x-pack/plugins/infra/server/services/log_entries/message/message.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { JsonArray, JsonValue } from '@kbn/common-utils';
import { LogMessagePart } from '../../../../common/log_entry';
-import { JsonArray, JsonValue } from '../../../../../../../src/plugins/kibana_utils/common';
import {
LogMessageFormattingCondition,
LogMessageFormattingFieldValueConditionValue,
diff --git a/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts b/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts
index 4760382fd9bdd..56d1b38e7e390 100644
--- a/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts
+++ b/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { JsonValue } from '../../../../../../../src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
export interface LogMessageFormattingRule {
when: LogMessageFormattingCondition;
diff --git a/x-pack/plugins/infra/server/utils/serialized_query.ts b/x-pack/plugins/infra/server/utils/serialized_query.ts
index 4f813d4fb137f..4169e123d8532 100644
--- a/x-pack/plugins/infra/server/utils/serialized_query.ts
+++ b/x-pack/plugins/infra/server/utils/serialized_query.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
export const parseFilterQuery = (
filterQuery: string | null | undefined
diff --git a/x-pack/plugins/infra/server/utils/typed_search_strategy.ts b/x-pack/plugins/infra/server/utils/typed_search_strategy.ts
index 546fd90a2da50..2482694474b0e 100644
--- a/x-pack/plugins/infra/server/utils/typed_search_strategy.ts
+++ b/x-pack/plugins/infra/server/utils/typed_search_strategy.ts
@@ -7,7 +7,7 @@
import * as rt from 'io-ts';
import stringify from 'json-stable-stringify';
-import { JsonValue } from '../../../../../src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
import { jsonValueRT } from '../../common/typed_json';
import { SearchStrategyError } from '../../common/search_strategies/common/errors';
import { ShardFailure } from './elasticsearch_runtime_types';
diff --git a/x-pack/plugins/ml/common/types/es_client.ts b/x-pack/plugins/ml/common/types/es_client.ts
index 67adda6b24c18..29a7a81aa5693 100644
--- a/x-pack/plugins/ml/common/types/es_client.ts
+++ b/x-pack/plugins/ml/common/types/es_client.ts
@@ -7,9 +7,9 @@
import { estypes } from '@elastic/elasticsearch';
+import { JsonObject } from '@kbn/common-utils';
import { buildEsQuery } from '../../../../../src/plugins/data/common/es_query/es_query';
import type { DslQuery } from '../../../../../src/plugins/data/common/es_query/kuery';
-import type { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
import { isPopulatedObject } from '../util/object_utils';
diff --git a/x-pack/plugins/osquery/common/typed_json.ts b/x-pack/plugins/osquery/common/typed_json.ts
index fb24b1dc0db5e..8ce6907beb80b 100644
--- a/x-pack/plugins/osquery/common/typed_json.ts
+++ b/x-pack/plugins/osquery/common/typed_json.ts
@@ -7,7 +7,7 @@
import { DslQuery, Filter } from 'src/plugins/data/common';
-import { JsonObject } from '../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
export type ESQuery =
| ESRangeQuery
diff --git a/x-pack/plugins/security_solution/common/typed_json.ts b/x-pack/plugins/security_solution/common/typed_json.ts
index fb24b1dc0db5e..8ce6907beb80b 100644
--- a/x-pack/plugins/security_solution/common/typed_json.ts
+++ b/x-pack/plugins/security_solution/common/typed_json.ts
@@ -7,7 +7,7 @@
import { DslQuery, Filter } from 'src/plugins/data/common';
-import { JsonObject } from '../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
export type ESQuery =
| ESRangeQuery
diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts
index bd026f486471f..a71524f9e02a8 100644
--- a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts
@@ -7,6 +7,7 @@
import { isEmpty, isString, flow } from 'lodash/fp';
+import { JsonObject } from '@kbn/common-utils';
import {
EsQueryConfig,
Query,
@@ -15,7 +16,6 @@ import {
esKuery,
IIndexPattern,
} from '../../../../../../../src/plugins/data/public';
-import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/public';
export const convertKueryToElasticSearchQuery = (
kueryExpression: string,
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 28a220c6f048a..70e74356188c7 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
@@ -6,10 +6,10 @@
*/
import type { IScopedClusterClient } from 'kibana/server';
+import { JsonObject } from '@kbn/common-utils';
import { parseFilterQuery } from '../../../../utils/serialized_query';
import { SafeResolverEvent } from '../../../../../common/endpoint/types';
import { PaginationBuilder } from '../utils/pagination';
-import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/common';
interface TimeRange {
from: string;
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts
index bf9b3ce6aa8f3..331f622951515 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/descendants.ts
@@ -7,8 +7,8 @@
import type { ApiResponse, estypes } from '@elastic/elasticsearch';
import { IScopedClusterClient } from 'src/core/server';
+import { JsonObject, JsonValue } from '@kbn/common-utils';
import { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types';
-import { JsonObject, JsonValue } from '../../../../../../../../../src/plugins/kibana_utils/common';
import { NodeID, TimeRange, docValueFields, validIDs } from '../utils/index';
interface DescendantsParams {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts
index f9780d1469756..7de038ccc9ae4 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/lifecycle.ts
@@ -6,8 +6,8 @@
*/
import { IScopedClusterClient } from 'src/core/server';
+import { JsonObject, JsonValue } from '@kbn/common-utils';
import { FieldsObject, ResolverSchema } from '../../../../../../common/endpoint/types';
-import { JsonObject, JsonValue } from '../../../../../../../../../src/plugins/kibana_utils/common';
import { NodeID, TimeRange, docValueFields, validIDs } from '../utils/index';
interface LifecycleParams {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts
index 24c97ad88b26a..f21259980d464 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/stats.ts
@@ -6,7 +6,7 @@
*/
import { IScopedClusterClient } from 'src/core/server';
-import { JsonObject } from '../../../../../../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { EventStats, ResolverSchema } from '../../../../../../common/endpoint/types';
import { NodeID, TimeRange } from '../utils/index';
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts
index befd69bdcf953..24fc447173ba6 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/pagination.ts
@@ -5,12 +5,12 @@
* 2.0.
*/
+import { JsonObject } from '@kbn/common-utils';
import { SafeResolverEvent } from '../../../../../common/endpoint/types';
import {
eventIDSafeVersion,
timestampSafeVersion,
} from '../../../../../common/endpoint/models/event';
-import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/common';
type SearchAfterFields = [number, string];
diff --git a/x-pack/plugins/security_solution/server/endpoint/types.ts b/x-pack/plugins/security_solution/server/endpoint/types.ts
index b3c7e58afe991..6076aa9af635b 100644
--- a/x-pack/plugins/security_solution/server/endpoint/types.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/types.ts
@@ -8,9 +8,9 @@
import { LoggerFactory } from 'kibana/server';
import { SearchResponse } from '@elastic/elasticsearch/api/types';
+import { JsonObject } from '@kbn/common-utils';
import { ConfigType } from '../config';
import { EndpointAppContextService } from './endpoint_app_context_services';
-import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
import { HostMetadata, MetadataQueryStrategyVersions } from '../../common/endpoint/types';
import { ExperimentalFeatures } from '../../common/experimental_features';
diff --git a/x-pack/plugins/security_solution/server/utils/serialized_query.ts b/x-pack/plugins/security_solution/server/utils/serialized_query.ts
index fb5009eefa318..7f8603ccab4b7 100644
--- a/x-pack/plugins/security_solution/server/utils/serialized_query.ts
+++ b/x-pack/plugins/security_solution/server/utils/serialized_query.ts
@@ -7,7 +7,7 @@
import { isEmpty, isPlainObject, isString } from 'lodash/fp';
-import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
export const parseFilterQuery = (filterQuery: string): JsonObject => {
try {
diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
index 35eb0dfca7a6b..073112f94e049 100644
--- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts
@@ -7,7 +7,7 @@
import { mapValues } from 'lodash';
import stats from 'stats-lite';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { RawMonitoringStats, RawMonitoredStat, HealthStatus } from './monitoring_stats_stream';
import { AveragedStat } from './task_run_calcultors';
import { TaskPersistenceTypes } from './task_run_statistics';
diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
index 7e13e25457ed6..fdf60fe6dda2c 100644
--- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
@@ -9,7 +9,7 @@ import { TaskManagerConfig } from '../config';
import { of, Subject } from 'rxjs';
import { take, bufferCount } from 'rxjs/operators';
import { createMonitoringStatsStream, AggregatedStat } from './monitoring_stats_stream';
-import { JsonValue } from 'src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
beforeEach(() => {
jest.resetAllMocks();
diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
index 8338bf3197162..78511f5a94ca0 100644
--- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
@@ -9,7 +9,7 @@ import { merge, of, Observable } from 'rxjs';
import { map, scan } from 'rxjs/operators';
import { set } from '@elastic/safer-lodash-set';
import { Logger } from 'src/core/server';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { TaskStore } from '../task_store';
import { TaskPollingLifecycle } from '../polling_lifecycle';
import {
diff --git a/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts b/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts
index 0a6db350a88b9..799ea054596c0 100644
--- a/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/runtime_statistics_aggregator.ts
@@ -6,7 +6,7 @@
*/
import { Observable } from 'rxjs';
-import { JsonValue } from 'src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
export interface AggregatedStat {
key: string;
diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts
index 4e2e689b71c88..b0611437d87be 100644
--- a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts
@@ -6,7 +6,7 @@
*/
import stats from 'stats-lite';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { isUndefined, countBy, mapValues } from 'lodash';
export interface AveragedStat extends JsonObject {
diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts
index eb6cb0796c33c..b792f4ca475f9 100644
--- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts
@@ -7,7 +7,7 @@
import { combineLatest, Observable } from 'rxjs';
import { filter, startWith, map } from 'rxjs/operators';
-import { JsonObject, JsonValue } from 'src/plugins/kibana_utils/common';
+import { JsonObject, JsonValue } from '@kbn/common-utils';
import { isNumber, mapValues } from 'lodash';
import { AggregatedStatProvider, AggregatedStat } from './runtime_statistics_aggregator';
import { TaskLifecycleEvent } from '../polling_lifecycle';
diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts
index 669f619832548..abd86be522f0c 100644
--- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts
@@ -8,7 +8,7 @@
import { combineLatest, Observable, timer } from 'rxjs';
import { mergeMap, map, filter, switchMap, catchError } from 'rxjs/operators';
import { Logger } from 'src/core/server';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { keyBy, mapValues } from 'lodash';
import { estypes } from '@elastic/elasticsearch';
import { AggregatedStatProvider } from './runtime_statistics_aggregator';
diff --git a/x-pack/plugins/task_manager/server/routes/health.ts b/x-pack/plugins/task_manager/server/routes/health.ts
index cc2f6c6630e56..0f43575d84481 100644
--- a/x-pack/plugins/task_manager/server/routes/health.ts
+++ b/x-pack/plugins/task_manager/server/routes/health.ts
@@ -16,7 +16,7 @@ import { Observable, Subject } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { throttleTime } from 'rxjs/operators';
import { isString } from 'lodash';
-import { JsonValue } from 'src/plugins/kibana_utils/common';
+import { JsonValue } from '@kbn/common-utils';
import { Logger, ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server';
import {
MonitoringStats,
diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
index 6a69921a36671..c5a6ef877c47a 100644
--- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
+++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts
@@ -8,10 +8,10 @@
import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import Mustache from 'mustache';
+import { JsonObject } from '@kbn/common-utils';
import { ActionGroupIdsOf } from '../../../../alerting/common';
import { UptimeAlertTypeFactory } from './types';
import { esKuery } from '../../../../../../src/plugins/data/server';
-import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common';
import {
StatusCheckFilters,
Ping,
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
index c126de27158cc..07047bd0be7bc 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { JsonObject } from 'src/plugins/kibana_utils/public';
+import { JsonObject } from '@kbn/common-utils';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types';
import { asMutableArray } from '../../../common/utils/as_mutable_array';
import { UMElasticsearchQueryFn } from '../adapters';
diff --git a/x-pack/test/apm_api_integration/tests/services/annotations.ts b/x-pack/test/apm_api_integration/tests/services/annotations.ts
index 9a634c9bf8247..34eadbe3c609c 100644
--- a/x-pack/test/apm_api_integration/tests/services/annotations.ts
+++ b/x-pack/test/apm_api_integration/tests/services/annotations.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { merge, cloneDeep, isPlainObject } from 'lodash';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { registry } from '../../common/registry';
diff --git a/x-pack/test/observability_api_integration/basic/tests/annotations.ts b/x-pack/test/observability_api_integration/basic/tests/annotations.ts
index 05bfba42dd59c..4a2c7b68f612e 100644
--- a/x-pack/test/observability_api_integration/basic/tests/annotations.ts
+++ b/x-pack/test/observability_api_integration/basic/tests/annotations.ts
@@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
diff --git a/x-pack/test/observability_api_integration/trial/tests/annotations.ts b/x-pack/test/observability_api_integration/trial/tests/annotations.ts
index 1ea3460060bc9..b1ef717ddfd88 100644
--- a/x-pack/test/observability_api_integration/trial/tests/annotations.ts
+++ b/x-pack/test/observability_api_integration/trial/tests/annotations.ts
@@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { Annotation } from '../../../../plugins/observability/common/annotations';
import { FtrProviderContext } from '../../common/ftr_provider_context';
diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts
index 073bc44e89e61..b3aeb55eb38a1 100644
--- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts
+++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts
@@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
-import { JsonObject } from 'src/plugins/kibana_utils/common';
+import { JsonObject } from '@kbn/common-utils';
import { eventsIndexPattern } from '../../../../plugins/security_solution/common/endpoint/constants';
import {
eventIDSafeVersion,
diff --git a/yarn.lock b/yarn.lock
index a9a81585000b5..4316e5f638c37 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2620,6 +2620,10 @@
version "0.0.0"
uid ""
+"@kbn/common-utils@link:bazel-bin/packages/kbn-common-utils":
+ version "0.0.0"
+ uid ""
+
"@kbn/config-schema@link:bazel-bin/packages/kbn-config-schema":
version "0.0.0"
uid ""
From c5e74d8241d645ccaf266bcb7d9c909a9c95b6df Mon Sep 17 00:00:00 2001
From: Madison Caldwell
Date: Wed, 16 Jun 2021 11:35:07 -0400
Subject: [PATCH 30/98] [RAC][Security Solution] Pull Gap Remediation out of
search_after_bulk_create (#102104)
* Modify threshold rules to receive a single date range tuple
* Modify threat match rules to receive a single date range tuple
* Modify custom query rules to receive a single date range tuple
* Fix up tests (partially)
* Change log message to indicate single tuple instead of array
* Bad test?
* Prevent max_signals from being exceeded on threat match rule executions
* Revert "Prevent max_signals from being exceeded on threat match rule executions"
This reverts commit ba3b2f7a382ef7c369f02c7939e1495f72d92bfe.
* Modify EQL rules to use date range tuple
* Modify ML rules to use date range tuple
* Fix ML/EQL tests
* Use dateMath to parse moments in ML/Threshold tests
* Add mocks for threshold test
* Use dateMath for eql tests
---
.../signals/executors/eql.test.ts | 10 +-
.../detection_engine/signals/executors/eql.ts | 7 +-
.../signals/executors/ml.test.ts | 12 +-
.../detection_engine/signals/executors/ml.ts | 8 +-
.../signals/executors/query.ts | 6 +-
.../signals/executors/threat_match.ts | 6 +-
.../signals/executors/threshold.test.ts | 25 +-
.../signals/executors/threshold.ts | 160 ++++++------
.../signals/search_after_bulk_create.test.ts | 37 ++-
.../signals/search_after_bulk_create.ts | 245 +++++++++---------
.../signals/signal_rule_alert_type.ts | 132 +++++-----
.../threat_mapping/create_threat_signal.ts | 4 +-
.../threat_mapping/create_threat_signals.ts | 4 +-
.../signals/threat_mapping/types.ts | 4 +-
.../lib/detection_engine/signals/types.ts | 4 +-
15 files changed, 352 insertions(+), 312 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts
index 947e7d573173e..e7af3d484dfbd 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import dateMath from '@elastic/datemath';
import { loggingSystemMock } from 'src/core/server/mocks';
import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks';
import { eqlExecutor } from './eql';
@@ -23,6 +24,7 @@ describe('eql_executor', () => {
let logger: ReturnType;
let alertServices: AlertServicesMock;
(getIndexVersion as jest.Mock).mockReturnValue(SIGNALS_TEMPLATE_VERSION);
+ const params = getEqlRuleParams();
const eqlSO = {
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
type: 'alert',
@@ -40,10 +42,15 @@ describe('eql_executor', () => {
interval: '5m',
},
throttle: 'no_actions',
- params: getEqlRuleParams(),
+ params,
},
references: [],
};
+ const tuple = {
+ from: dateMath.parse(params.from)!,
+ to: dateMath.parse(params.to)!,
+ maxSignals: params.maxSignals,
+ };
const searchAfterSize = 7;
beforeEach(() => {
@@ -64,6 +71,7 @@ describe('eql_executor', () => {
const exceptionItems = [getExceptionListItemSchemaMock({ entries: [getEntryListMock()] })];
const response = await eqlExecutor({
rule: eqlSO,
+ tuple,
exceptionItems,
services: alertServices,
version,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts
index 28d1f3e19baee..a187b73069682 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts
@@ -28,6 +28,7 @@ import {
AlertAttributes,
BulkCreate,
EqlSignalSearchResponse,
+ RuleRangeTuple,
SearchAfterAndBulkCreateReturnType,
WrappedSignalHit,
} from '../types';
@@ -35,6 +36,7 @@ import { createSearchAfterReturnType, makeFloatString, wrapSignal } from '../uti
export const eqlExecutor = async ({
rule,
+ tuple,
exceptionItems,
services,
version,
@@ -43,6 +45,7 @@ export const eqlExecutor = async ({
bulkCreate,
}: {
rule: SavedObject>;
+ tuple: RuleRangeTuple;
exceptionItems: ExceptionListItemSchema[];
services: AlertServices;
version: string;
@@ -81,8 +84,8 @@ export const eqlExecutor = async ({
const request = buildEqlSearchRequest(
ruleParams.query,
inputIndex,
- ruleParams.from,
- ruleParams.to,
+ tuple.from.toISOString(),
+ tuple.to.toISOString(),
searchAfterSize,
ruleParams.timestampOverride,
exceptionItems,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts
index 25a9d2c3f510f..89c1392cb67ba 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import dateMath from '@elastic/datemath';
import { loggingSystemMock } from 'src/core/server/mocks';
import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks';
import { mlExecutor } from './ml';
@@ -26,7 +27,13 @@ describe('ml_executor', () => {
const exceptionItems = [getExceptionListItemSchemaMock()];
let logger: ReturnType;
let alertServices: AlertServicesMock;
- const mlSO = sampleRuleSO(getMlRuleParams());
+ const params = getMlRuleParams();
+ const mlSO = sampleRuleSO(params);
+ const tuple = {
+ from: dateMath.parse(params.from)!,
+ to: dateMath.parse(params.to)!,
+ maxSignals: params.maxSignals,
+ };
const buildRuleMessage = buildRuleMessageFactory({
id: mlSO.id,
ruleId: mlSO.attributes.params.ruleId,
@@ -60,6 +67,7 @@ describe('ml_executor', () => {
await expect(
mlExecutor({
rule: mlSO,
+ tuple,
ml: undefined,
exceptionItems,
services: alertServices,
@@ -76,6 +84,7 @@ describe('ml_executor', () => {
jobsSummaryMock.mockResolvedValue([]);
const response = await mlExecutor({
rule: mlSO,
+ tuple,
ml: mlMock,
exceptionItems,
services: alertServices,
@@ -101,6 +110,7 @@ describe('ml_executor', () => {
const response = await mlExecutor({
rule: mlSO,
+ tuple,
ml: mlMock,
exceptionItems,
services: alertServices,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts
index f5c7d8822b51f..20c4cb16dadc8 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts
@@ -21,11 +21,12 @@ import { bulkCreateMlSignals } from '../bulk_create_ml_signals';
import { filterEventsAgainstList } from '../filters/filter_events_against_list';
import { findMlSignals } from '../find_ml_signals';
import { BuildRuleMessage } from '../rule_messages';
-import { AlertAttributes, BulkCreate, WrapHits } from '../types';
+import { AlertAttributes, BulkCreate, RuleRangeTuple, WrapHits } from '../types';
import { createErrorsFromShard, createSearchAfterReturnType, mergeReturns } from '../utils';
export const mlExecutor = async ({
rule,
+ tuple,
ml,
listClient,
exceptionItems,
@@ -36,6 +37,7 @@ export const mlExecutor = async ({
wrapHits,
}: {
rule: SavedObject>;
+ tuple: RuleRangeTuple;
ml: SetupPlugins['ml'];
listClient: ListClient;
exceptionItems: ExceptionListItemSchema[];
@@ -88,8 +90,8 @@ export const mlExecutor = async ({
savedObjectsClient: services.savedObjectsClient,
jobIds: ruleParams.machineLearningJobId,
anomalyThreshold: ruleParams.anomalyThreshold,
- from: ruleParams.from,
- to: ruleParams.to,
+ from: tuple.from.toISOString(),
+ to: tuple.to.toISOString(),
exceptionItems,
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts
index 9d76a06afa275..385c01c2f1cda 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts
@@ -24,7 +24,7 @@ import { QueryRuleParams, SavedQueryRuleParams } from '../../schemas/rule_schema
export const queryExecutor = async ({
rule,
- tuples,
+ tuple,
listClient,
exceptionItems,
services,
@@ -37,7 +37,7 @@ export const queryExecutor = async ({
wrapHits,
}: {
rule: SavedObject>;
- tuples: RuleRangeTuple[];
+ tuple: RuleRangeTuple;
listClient: ListClient;
exceptionItems: ExceptionListItemSchema[];
services: AlertServices;
@@ -63,7 +63,7 @@ export const queryExecutor = async ({
});
return searchAfterAndBulkCreate({
- tuples,
+ tuple,
listClient,
exceptionsList: exceptionItems,
ruleSO: rule,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts
index 078eb8362069c..d0e22f696b222 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts
@@ -23,7 +23,7 @@ import { ThreatRuleParams } from '../../schemas/rule_schemas';
export const threatMatchExecutor = async ({
rule,
- tuples,
+ tuple,
listClient,
exceptionItems,
services,
@@ -36,7 +36,7 @@ export const threatMatchExecutor = async ({
wrapHits,
}: {
rule: SavedObject>;
- tuples: RuleRangeTuple[];
+ tuple: RuleRangeTuple;
listClient: ListClient;
exceptionItems: ExceptionListItemSchema[];
services: AlertServices;
@@ -51,7 +51,7 @@ export const threatMatchExecutor = async ({
const ruleParams = rule.attributes.params;
const inputIndex = await getInputIndex(services, version, ruleParams.index);
return createThreatSignals({
- tuples,
+ tuple,
threatMapping: ruleParams.threatMapping,
query: ruleParams.query,
inputIndex,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts
index f03e8b8a147ae..3906c66922238 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts
@@ -5,18 +5,23 @@
* 2.0.
*/
+import dateMath from '@elastic/datemath';
import { loggingSystemMock } from 'src/core/server/mocks';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks';
import { thresholdExecutor } from './threshold';
import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { getEntryListMock } from '../../../../../../lists/common/schemas/types/entry_list.mock';
import { getThresholdRuleParams } from '../../schemas/rule_schemas.mock';
import { buildRuleMessageFactory } from '../rule_messages';
+import { sampleEmptyDocSearchResults } from '../__mocks__/es_results';
describe('threshold_executor', () => {
const version = '8.0.0';
let logger: ReturnType;
let alertServices: AlertServicesMock;
+ const params = getThresholdRuleParams();
const thresholdSO = {
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
type: 'alert',
@@ -34,10 +39,15 @@ describe('threshold_executor', () => {
interval: '5m',
},
throttle: 'no_actions',
- params: getThresholdRuleParams(),
+ params,
},
references: [],
};
+ const tuple = {
+ from: dateMath.parse(params.from)!,
+ to: dateMath.parse(params.to)!,
+ maxSignals: params.maxSignals,
+ };
const buildRuleMessage = buildRuleMessageFactory({
id: thresholdSO.id,
ruleId: thresholdSO.attributes.params.ruleId,
@@ -47,6 +57,9 @@ describe('threshold_executor', () => {
beforeEach(() => {
alertServices = alertsMock.createAlertServices();
+ alertServices.scopedClusterClient.asCurrentUser.search.mockResolvedValue(
+ elasticsearchClientMock.createSuccessTransportRequestPromise(sampleEmptyDocSearchResults())
+ );
logger = loggingSystemMock.createLogger();
});
@@ -55,14 +68,20 @@ describe('threshold_executor', () => {
const exceptionItems = [getExceptionListItemSchemaMock({ entries: [getEntryListMock()] })];
const response = await thresholdExecutor({
rule: thresholdSO,
- tuples: [],
+ tuple,
exceptionItems,
services: alertServices,
version,
logger,
buildRuleMessage,
startedAt: new Date(),
- bulkCreate: jest.fn(),
+ bulkCreate: jest.fn().mockImplementation((hits) => ({
+ errors: [],
+ success: true,
+ bulkCreateDuration: '0',
+ createdItemsCount: 0,
+ createdItems: [],
+ })),
wrapHits: jest.fn(),
});
expect(response.warningMessages.length).toEqual(1);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts
index 5e23128c9c148..378d68fc13d2a 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts
@@ -39,7 +39,7 @@ import { BuildRuleMessage } from '../rule_messages';
export const thresholdExecutor = async ({
rule,
- tuples,
+ tuple,
exceptionItems,
services,
version,
@@ -50,7 +50,7 @@ export const thresholdExecutor = async ({
wrapHits,
}: {
rule: SavedObject>;
- tuples: RuleRangeTuple[];
+ tuple: RuleRangeTuple;
exceptionItems: ExceptionListItemSchema[];
services: AlertServices;
version: string;
@@ -70,90 +70,88 @@ export const thresholdExecutor = async ({
}
const inputIndex = await getInputIndex(services, version, ruleParams.index);
- for (const tuple of tuples) {
- const {
- thresholdSignalHistory,
- searchErrors: previousSearchErrors,
- } = await getThresholdSignalHistory({
- indexPattern: [ruleParams.outputIndex],
- from: tuple.from.toISOString(),
- to: tuple.to.toISOString(),
- services,
- logger,
- ruleId: ruleParams.ruleId,
- bucketByFields: ruleParams.threshold.field,
- timestampOverride: ruleParams.timestampOverride,
- buildRuleMessage,
- });
+ const {
+ thresholdSignalHistory,
+ searchErrors: previousSearchErrors,
+ } = await getThresholdSignalHistory({
+ indexPattern: [ruleParams.outputIndex],
+ from: tuple.from.toISOString(),
+ to: tuple.to.toISOString(),
+ services,
+ logger,
+ ruleId: ruleParams.ruleId,
+ bucketByFields: ruleParams.threshold.field,
+ timestampOverride: ruleParams.timestampOverride,
+ buildRuleMessage,
+ });
- const bucketFilters = await getThresholdBucketFilters({
- thresholdSignalHistory,
- timestampOverride: ruleParams.timestampOverride,
- });
+ const bucketFilters = await getThresholdBucketFilters({
+ thresholdSignalHistory,
+ timestampOverride: ruleParams.timestampOverride,
+ });
+
+ const esFilter = await getFilter({
+ type: ruleParams.type,
+ filters: ruleParams.filters ? ruleParams.filters.concat(bucketFilters) : bucketFilters,
+ language: ruleParams.language,
+ query: ruleParams.query,
+ savedId: ruleParams.savedId,
+ services,
+ index: inputIndex,
+ lists: exceptionItems,
+ });
+
+ const {
+ searchResult: thresholdResults,
+ searchErrors,
+ searchDuration: thresholdSearchDuration,
+ } = await findThresholdSignals({
+ inputIndexPattern: inputIndex,
+ from: tuple.from.toISOString(),
+ to: tuple.to.toISOString(),
+ services,
+ logger,
+ filter: esFilter,
+ threshold: ruleParams.threshold,
+ timestampOverride: ruleParams.timestampOverride,
+ buildRuleMessage,
+ });
- const esFilter = await getFilter({
- type: ruleParams.type,
- filters: ruleParams.filters ? ruleParams.filters.concat(bucketFilters) : bucketFilters,
- language: ruleParams.language,
- query: ruleParams.query,
- savedId: ruleParams.savedId,
- services,
- index: inputIndex,
- lists: exceptionItems,
- });
+ const {
+ success,
+ bulkCreateDuration,
+ createdItemsCount,
+ createdItems,
+ errors,
+ } = await bulkCreateThresholdSignals({
+ someResult: thresholdResults,
+ ruleSO: rule,
+ filter: esFilter,
+ services,
+ logger,
+ inputIndexPattern: inputIndex,
+ signalsIndex: ruleParams.outputIndex,
+ startedAt,
+ from: tuple.from.toDate(),
+ thresholdSignalHistory,
+ bulkCreate,
+ wrapHits,
+ });
- const {
+ result = mergeReturns([
+ result,
+ createSearchAfterReturnTypeFromResponse({
searchResult: thresholdResults,
- searchErrors,
- searchDuration: thresholdSearchDuration,
- } = await findThresholdSignals({
- inputIndexPattern: inputIndex,
- from: tuple.from.toISOString(),
- to: tuple.to.toISOString(),
- services,
- logger,
- filter: esFilter,
- threshold: ruleParams.threshold,
timestampOverride: ruleParams.timestampOverride,
- buildRuleMessage,
- });
-
- const {
+ }),
+ createSearchAfterReturnType({
success,
- bulkCreateDuration,
- createdItemsCount,
- createdItems,
- errors,
- } = await bulkCreateThresholdSignals({
- someResult: thresholdResults,
- ruleSO: rule,
- filter: esFilter,
- services,
- logger,
- inputIndexPattern: inputIndex,
- signalsIndex: ruleParams.outputIndex,
- startedAt,
- from: tuple.from.toDate(),
- thresholdSignalHistory,
- bulkCreate,
- wrapHits,
- });
-
- result = mergeReturns([
- result,
- createSearchAfterReturnTypeFromResponse({
- searchResult: thresholdResults,
- timestampOverride: ruleParams.timestampOverride,
- }),
- createSearchAfterReturnType({
- success,
- errors: [...errors, ...previousSearchErrors, ...searchErrors],
- createdSignalsCount: createdItemsCount,
- createdSignals: createdItems,
- bulkCreateTimes: bulkCreateDuration ? [bulkCreateDuration] : [],
- searchAfterTimes: [thresholdSearchDuration],
- }),
- ]);
- }
+ errors: [...errors, ...previousSearchErrors, ...searchErrors],
+ createdSignalsCount: createdItemsCount,
+ createdSignals: createdItems,
+ bulkCreateTimes: bulkCreateDuration ? [bulkCreateDuration] : [],
+ searchAfterTimes: [thresholdSearchDuration],
+ }),
+ ]);
return result;
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts
index e4eb7e854f670..184b49c2d6c7b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts
@@ -44,14 +44,14 @@ describe('searchAfterAndBulkCreate', () => {
const sampleParams = getQueryRuleParams();
const ruleSO = sampleRuleSO(getQueryRuleParams());
sampleParams.maxSignals = 30;
- let tuples: RuleRangeTuple[];
+ let tuple: RuleRangeTuple;
beforeEach(() => {
jest.clearAllMocks();
listClient = listMock.getListClient();
listClient.searchListItemByValues = jest.fn().mockResolvedValue([]);
inputIndexPattern = ['auditbeat-*'];
mockService = alertsMock.createAlertServices();
- ({ tuples } = getRuleRangeTuples({
+ tuple = getRuleRangeTuples({
logger: mockLogger,
previousStartedAt: new Date(),
from: sampleParams.from,
@@ -59,7 +59,7 @@ describe('searchAfterAndBulkCreate', () => {
interval: '5m',
maxSignals: sampleParams.maxSignals,
buildRuleMessage,
- }));
+ }).tuples[0];
bulkCreate = bulkCreateFactory(
mockLogger,
mockService.scopedClusterClient.asCurrentUser,
@@ -174,7 +174,7 @@ describe('searchAfterAndBulkCreate', () => {
];
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
- tuples,
+ tuple,
ruleSO,
listClient,
exceptionsList: [exceptionItem],
@@ -279,7 +279,7 @@ describe('searchAfterAndBulkCreate', () => {
];
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [exceptionItem],
services: mockService,
@@ -357,7 +357,7 @@ describe('searchAfterAndBulkCreate', () => {
];
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [exceptionItem],
services: mockService,
@@ -416,7 +416,7 @@ describe('searchAfterAndBulkCreate', () => {
];
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [exceptionItem],
services: mockService,
@@ -495,7 +495,7 @@ describe('searchAfterAndBulkCreate', () => {
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [],
services: mockService,
@@ -550,7 +550,7 @@ describe('searchAfterAndBulkCreate', () => {
];
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [exceptionItem],
services: mockService,
@@ -569,11 +569,6 @@ describe('searchAfterAndBulkCreate', () => {
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
expect(createdSignalsCount).toEqual(0); // should not create any signals because all events were in the allowlist
expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000'));
- // I don't like testing log statements since logs change but this is the best
- // way I can think of to ensure this section is getting hit with this test case.
- expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[7][0]).toContain(
- 'ran out of sort ids to sort on name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"'
- );
});
test('should return success when no sortId present but search results are in the allowlist', async () => {
@@ -627,7 +622,7 @@ describe('searchAfterAndBulkCreate', () => {
];
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [exceptionItem],
services: mockService,
@@ -701,7 +696,7 @@ describe('searchAfterAndBulkCreate', () => {
);
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [],
services: mockService,
@@ -746,7 +741,7 @@ describe('searchAfterAndBulkCreate', () => {
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
listClient,
exceptionsList: [exceptionItem],
- tuples,
+ tuple,
ruleSO,
services: mockService,
logger: mockLogger,
@@ -793,7 +788,7 @@ describe('searchAfterAndBulkCreate', () => {
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
listClient,
exceptionsList: [exceptionItem],
- tuples,
+ tuple,
ruleSO,
services: mockService,
logger: mockLogger,
@@ -854,7 +849,7 @@ describe('searchAfterAndBulkCreate', () => {
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
listClient,
exceptionsList: [exceptionItem],
- tuples,
+ tuple,
ruleSO,
services: mockService,
logger: mockLogger,
@@ -979,7 +974,7 @@ describe('searchAfterAndBulkCreate', () => {
errors,
} = await searchAfterAndBulkCreate({
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [],
services: mockService,
@@ -1075,7 +1070,7 @@ describe('searchAfterAndBulkCreate', () => {
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
enrichment: mockEnrichment,
ruleSO,
- tuples,
+ tuple,
listClient,
exceptionsList: [],
services: mockService,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts
index bb2e57b0606e5..eb4af0c38ce25 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts
@@ -23,7 +23,7 @@ import { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType } fr
// search_after through documents and re-index using bulk endpoint.
export const searchAfterAndBulkCreate = async ({
- tuples: totalToFromTuples,
+ tuple,
ruleSO,
exceptionsList,
services,
@@ -49,150 +49,143 @@ export const searchAfterAndBulkCreate = async ({
// to ensure we don't exceed maxSignals
let signalsCreatedCount = 0;
- const tuplesToBeLogged = [...totalToFromTuples];
- logger.debug(buildRuleMessage(`totalToFromTuples: ${totalToFromTuples.length}`));
-
- while (totalToFromTuples.length > 0) {
- const tuple = totalToFromTuples.pop();
- if (tuple == null || tuple.to == null || tuple.from == null) {
- logger.error(buildRuleMessage(`[-] malformed date tuple`));
- return createSearchAfterReturnType({
- success: false,
- errors: ['malformed date tuple'],
- });
- }
- signalsCreatedCount = 0;
- while (signalsCreatedCount < tuple.maxSignals) {
- try {
- let mergedSearchResults = createSearchResultReturnType();
- logger.debug(buildRuleMessage(`sortIds: ${sortIds}`));
+ if (tuple == null || tuple.to == null || tuple.from == null) {
+ logger.error(buildRuleMessage(`[-] malformed date tuple`));
+ return createSearchAfterReturnType({
+ success: false,
+ errors: ['malformed date tuple'],
+ });
+ }
+ signalsCreatedCount = 0;
+ while (signalsCreatedCount < tuple.maxSignals) {
+ try {
+ let mergedSearchResults = createSearchResultReturnType();
+ logger.debug(buildRuleMessage(`sortIds: ${sortIds}`));
- if (hasSortId) {
- const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({
- buildRuleMessage,
- searchAfterSortIds: sortIds,
- index: inputIndexPattern,
- from: tuple.from.toISOString(),
- to: tuple.to.toISOString(),
- services,
- logger,
- // @ts-expect-error please, declare a type explicitly instead of unknown
- filter,
- pageSize: Math.ceil(Math.min(tuple.maxSignals, pageSize)),
+ if (hasSortId) {
+ const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({
+ buildRuleMessage,
+ searchAfterSortIds: sortIds,
+ index: inputIndexPattern,
+ from: tuple.from.toISOString(),
+ to: tuple.to.toISOString(),
+ services,
+ logger,
+ // @ts-expect-error please, declare a type explicitly instead of unknown
+ filter,
+ pageSize: Math.ceil(Math.min(tuple.maxSignals, pageSize)),
+ timestampOverride: ruleParams.timestampOverride,
+ });
+ mergedSearchResults = mergeSearchResults([mergedSearchResults, searchResult]);
+ toReturn = mergeReturns([
+ toReturn,
+ createSearchAfterReturnTypeFromResponse({
+ searchResult: mergedSearchResults,
timestampOverride: ruleParams.timestampOverride,
- });
- mergedSearchResults = mergeSearchResults([mergedSearchResults, searchResult]);
- toReturn = mergeReturns([
- toReturn,
- createSearchAfterReturnTypeFromResponse({
- searchResult: mergedSearchResults,
- timestampOverride: ruleParams.timestampOverride,
- }),
- createSearchAfterReturnType({
- searchAfterTimes: [searchDuration],
- errors: searchErrors,
- }),
- ]);
+ }),
+ createSearchAfterReturnType({
+ searchAfterTimes: [searchDuration],
+ errors: searchErrors,
+ }),
+ ]);
- const lastSortIds = getSafeSortIds(
- searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort
- );
- if (lastSortIds != null && lastSortIds.length !== 0) {
- sortIds = lastSortIds;
- hasSortId = true;
- } else {
- hasSortId = false;
- }
+ const lastSortIds = getSafeSortIds(
+ searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort
+ );
+ if (lastSortIds != null && lastSortIds.length !== 0) {
+ sortIds = lastSortIds;
+ hasSortId = true;
+ } else {
+ hasSortId = false;
}
+ }
+
+ // determine if there are any candidate signals to be processed
+ const totalHits = createTotalHitsFromSearchResult({ searchResult: mergedSearchResults });
+ logger.debug(buildRuleMessage(`totalHits: ${totalHits}`));
+ logger.debug(
+ buildRuleMessage(`searchResult.hit.hits.length: ${mergedSearchResults.hits.hits.length}`)
+ );
- // determine if there are any candidate signals to be processed
- const totalHits = createTotalHitsFromSearchResult({ searchResult: mergedSearchResults });
- logger.debug(buildRuleMessage(`totalHits: ${totalHits}`));
+ if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) {
logger.debug(
- buildRuleMessage(`searchResult.hit.hits.length: ${mergedSearchResults.hits.hits.length}`)
+ buildRuleMessage(
+ `${
+ totalHits === 0 ? 'totalHits' : 'searchResult.hits.hits.length'
+ } was 0, exiting early`
+ )
);
+ break;
+ }
- if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) {
- logger.debug(
- buildRuleMessage(
- `${
- totalHits === 0 ? 'totalHits' : 'searchResult.hits.hits.length'
- } was 0, exiting and moving on to next tuple`
- )
- );
- break;
- }
-
- // filter out the search results that match with the values found in the list.
- // the resulting set are signals to be indexed, given they are not duplicates
- // of signals already present in the signals index.
- const filteredEvents = await filterEventsAgainstList({
- listClient,
- exceptionsList,
- logger,
- eventSearchResult: mergedSearchResults,
- buildRuleMessage,
- });
-
- // only bulk create if there are filteredEvents leftover
- // if there isn't anything after going through the value list filter
- // skip the call to bulk create and proceed to the next search_after,
- // if there is a sort id to continue the search_after with.
- if (filteredEvents.hits.hits.length !== 0) {
- // make sure we are not going to create more signals than maxSignals allows
- if (signalsCreatedCount + filteredEvents.hits.hits.length > tuple.maxSignals) {
- filteredEvents.hits.hits = filteredEvents.hits.hits.slice(
- 0,
- tuple.maxSignals - signalsCreatedCount
- );
- }
- const enrichedEvents = await enrichment(filteredEvents);
- const wrappedDocs = wrapHits(enrichedEvents.hits.hits);
+ // filter out the search results that match with the values found in the list.
+ // the resulting set are signals to be indexed, given they are not duplicates
+ // of signals already present in the signals index.
+ const filteredEvents = await filterEventsAgainstList({
+ listClient,
+ exceptionsList,
+ logger,
+ eventSearchResult: mergedSearchResults,
+ buildRuleMessage,
+ });
- const {
- bulkCreateDuration: bulkDuration,
- createdItemsCount: createdCount,
- createdItems,
- success: bulkSuccess,
- errors: bulkErrors,
- } = await bulkCreate(wrappedDocs);
- toReturn = mergeReturns([
- toReturn,
- createSearchAfterReturnType({
- success: bulkSuccess,
- createdSignalsCount: createdCount,
- createdSignals: createdItems,
- bulkCreateTimes: bulkDuration ? [bulkDuration] : undefined,
- errors: bulkErrors,
- }),
- ]);
- signalsCreatedCount += createdCount;
- logger.debug(buildRuleMessage(`created ${createdCount} signals`));
- logger.debug(buildRuleMessage(`signalsCreatedCount: ${signalsCreatedCount}`));
- logger.debug(
- buildRuleMessage(`enrichedEvents.hits.hits: ${enrichedEvents.hits.hits.length}`)
+ // only bulk create if there are filteredEvents leftover
+ // if there isn't anything after going through the value list filter
+ // skip the call to bulk create and proceed to the next search_after,
+ // if there is a sort id to continue the search_after with.
+ if (filteredEvents.hits.hits.length !== 0) {
+ // make sure we are not going to create more signals than maxSignals allows
+ if (signalsCreatedCount + filteredEvents.hits.hits.length > tuple.maxSignals) {
+ filteredEvents.hits.hits = filteredEvents.hits.hits.slice(
+ 0,
+ tuple.maxSignals - signalsCreatedCount
);
-
- sendAlertTelemetryEvents(logger, eventsTelemetry, enrichedEvents, buildRuleMessage);
}
+ const enrichedEvents = await enrichment(filteredEvents);
+ const wrappedDocs = wrapHits(enrichedEvents.hits.hits);
- if (!hasSortId) {
- logger.debug(buildRuleMessage('ran out of sort ids to sort on'));
- break;
- }
- } catch (exc: unknown) {
- logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`));
- return mergeReturns([
+ const {
+ bulkCreateDuration: bulkDuration,
+ createdItemsCount: createdCount,
+ createdItems,
+ success: bulkSuccess,
+ errors: bulkErrors,
+ } = await bulkCreate(wrappedDocs);
+ toReturn = mergeReturns([
toReturn,
createSearchAfterReturnType({
- success: false,
- errors: [`${exc}`],
+ success: bulkSuccess,
+ createdSignalsCount: createdCount,
+ createdSignals: createdItems,
+ bulkCreateTimes: bulkDuration ? [bulkDuration] : undefined,
+ errors: bulkErrors,
}),
]);
+ signalsCreatedCount += createdCount;
+ logger.debug(buildRuleMessage(`created ${createdCount} signals`));
+ logger.debug(buildRuleMessage(`signalsCreatedCount: ${signalsCreatedCount}`));
+ logger.debug(
+ buildRuleMessage(`enrichedEvents.hits.hits: ${enrichedEvents.hits.hits.length}`)
+ );
+
+ sendAlertTelemetryEvents(logger, eventsTelemetry, enrichedEvents, buildRuleMessage);
+ }
+
+ if (!hasSortId) {
+ logger.debug(buildRuleMessage('ran out of sort ids to sort on'));
+ break;
}
+ } catch (exc: unknown) {
+ logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`));
+ return mergeReturns([
+ toReturn,
+ createSearchAfterReturnType({
+ success: false,
+ errors: [`${exc}`],
+ }),
+ ]);
}
}
logger.debug(buildRuleMessage(`[+] completed bulk index of ${toReturn.createdSignalsCount}`));
- toReturn.totalToFromTuples = tuplesToBeLogged;
return toReturn;
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 0a2e22bc44b60..bb1e50c14d401 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -235,74 +235,86 @@ export const signalRulesAlertType = ({
if (isMlRule(type)) {
const mlRuleSO = asTypeSpecificSO(savedObject, machineLearningRuleParams);
- result = await mlExecutor({
- rule: mlRuleSO,
- ml,
- listClient,
- exceptionItems,
- services,
- logger,
- buildRuleMessage,
- bulkCreate,
- wrapHits,
- });
+ for (const tuple of tuples) {
+ result = await mlExecutor({
+ rule: mlRuleSO,
+ tuple,
+ ml,
+ listClient,
+ exceptionItems,
+ services,
+ logger,
+ buildRuleMessage,
+ bulkCreate,
+ wrapHits,
+ });
+ }
} else if (isThresholdRule(type)) {
const thresholdRuleSO = asTypeSpecificSO(savedObject, thresholdRuleParams);
- result = await thresholdExecutor({
- rule: thresholdRuleSO,
- tuples,
- exceptionItems,
- services,
- version,
- logger,
- buildRuleMessage,
- startedAt,
- bulkCreate,
- wrapHits,
- });
+ for (const tuple of tuples) {
+ result = await thresholdExecutor({
+ rule: thresholdRuleSO,
+ tuple,
+ exceptionItems,
+ services,
+ version,
+ logger,
+ buildRuleMessage,
+ startedAt,
+ bulkCreate,
+ wrapHits,
+ });
+ }
} else if (isThreatMatchRule(type)) {
const threatRuleSO = asTypeSpecificSO(savedObject, threatRuleParams);
- result = await threatMatchExecutor({
- rule: threatRuleSO,
- tuples,
- listClient,
- exceptionItems,
- services,
- version,
- searchAfterSize,
- logger,
- eventsTelemetry,
- buildRuleMessage,
- bulkCreate,
- wrapHits,
- });
+ for (const tuple of tuples) {
+ result = await threatMatchExecutor({
+ rule: threatRuleSO,
+ tuple,
+ listClient,
+ exceptionItems,
+ services,
+ version,
+ searchAfterSize,
+ logger,
+ eventsTelemetry,
+ buildRuleMessage,
+ bulkCreate,
+ wrapHits,
+ });
+ }
} else if (isQueryRule(type)) {
const queryRuleSO = validateQueryRuleTypes(savedObject);
- result = await queryExecutor({
- rule: queryRuleSO,
- tuples,
- listClient,
- exceptionItems,
- services,
- version,
- searchAfterSize,
- logger,
- eventsTelemetry,
- buildRuleMessage,
- bulkCreate,
- wrapHits,
- });
+ for (const tuple of tuples) {
+ result = await queryExecutor({
+ rule: queryRuleSO,
+ tuple,
+ listClient,
+ exceptionItems,
+ services,
+ version,
+ searchAfterSize,
+ logger,
+ eventsTelemetry,
+ buildRuleMessage,
+ bulkCreate,
+ wrapHits,
+ });
+ }
} else if (isEqlRule(type)) {
const eqlRuleSO = asTypeSpecificSO(savedObject, eqlRuleParams);
- result = await eqlExecutor({
- rule: eqlRuleSO,
- exceptionItems,
- services,
- version,
- searchAfterSize,
- bulkCreate,
- logger,
- });
+ for (const tuple of tuples) {
+ result = await eqlExecutor({
+ rule: eqlRuleSO,
+ tuple,
+ exceptionItems,
+ services,
+ version,
+ searchAfterSize,
+ bulkCreate,
+ logger,
+ });
+ }
} else {
throw new Error(`unknown rule type ${type}`);
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts
index 3e30a08f1ae69..806f5e47608e4 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts
@@ -13,7 +13,7 @@ import { CreateThreatSignalOptions } from './types';
import { SearchAfterAndBulkCreateReturnType } from '../types';
export const createThreatSignal = async ({
- tuples,
+ tuple,
threatMapping,
threatEnrichment,
query,
@@ -70,7 +70,7 @@ export const createThreatSignal = async ({
);
const result = await searchAfterAndBulkCreate({
- tuples,
+ tuple,
listClient,
exceptionsList: exceptionItems,
ruleSO,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
index 5054ab1b2cca5..169a820392a6e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
@@ -15,7 +15,7 @@ import { combineConcurrentResults } from './utils';
import { buildThreatEnrichment } from './build_threat_enrichment';
export const createThreatSignals = async ({
- tuples,
+ tuple,
threatMapping,
query,
inputIndex,
@@ -104,7 +104,7 @@ export const createThreatSignals = async ({
const concurrentSearchesPerformed = chunks.map>(
(slicedChunk) =>
createThreatSignal({
- tuples,
+ tuple,
threatEnrichment,
threatMapping,
query,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
index 34b064b0f8805..ded79fc647ac4 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
@@ -40,7 +40,7 @@ import { ThreatRuleParams } from '../../schemas/rule_schemas';
export type SortOrderOrUndefined = 'asc' | 'desc' | undefined;
export interface CreateThreatSignalsOptions {
- tuples: RuleRangeTuple[];
+ tuple: RuleRangeTuple;
threatMapping: ThreatMapping;
query: string;
inputIndex: string[];
@@ -70,7 +70,7 @@ export interface CreateThreatSignalsOptions {
}
export interface CreateThreatSignalOptions {
- tuples: RuleRangeTuple[];
+ tuple: RuleRangeTuple;
threatMapping: ThreatMapping;
threatEnrichment: SignalsEnrichment;
query: string;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts
index c35eb04ba1270..8a6ce91b2575a 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts
@@ -262,11 +262,11 @@ export type WrapHits = (
) => Array>;
export interface SearchAfterAndBulkCreateParams {
- tuples: Array<{
+ tuple: {
to: moment.Moment;
from: moment.Moment;
maxSignals: number;
- }>;
+ };
ruleSO: SavedObject;
services: AlertServices;
listClient: ListClient;
From 973d0578461a8823eb529b38ee7a5edc427b93a0 Mon Sep 17 00:00:00 2001
From: Thomas Watson
Date: Wed, 16 Jun 2021 18:01:53 +0200
Subject: [PATCH 31/98] Upgrade normalize-url from v4.5.0 to v4.5.1 (#102291)
---
yarn.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 4316e5f638c37..353527731cb04 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -20304,9 +20304,9 @@ normalize-url@^3.0.0:
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
normalize-url@^4.1.0:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
- integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
+ integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
now-and-later@^2.0.0:
version "2.0.0"
From d2e81ee785df812135faf775e14f7aaaf58eb901 Mon Sep 17 00:00:00 2001
From: John Dorlus
Date: Wed, 16 Jun 2021 12:07:50 -0400
Subject: [PATCH 32/98] CIT for circle processor (renewed PR) (#102277)
* Added CITs for Circle processor.
* Fixed issue with form function using int instead of string.
* Added changed per nits.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../__jest__/processors/circle.test.tsx | 114 ++++++++++++++++++
.../__jest__/processors/processor.helpers.tsx | 2 +
.../processor_form/processors/circle.tsx | 2 +
3 files changed, 118 insertions(+)
create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx
new file mode 100644
index 0000000000000..e29bb2ac6e92e
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx
@@ -0,0 +1,114 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { act } from 'react-dom/test-utils';
+import { setup, SetupResult, getProcessorValue } from './processor.helpers';
+
+const CIRCLE_TYPE = 'circle';
+
+describe('Processor: Circle', () => {
+ let onUpdate: jest.Mock;
+ let testBed: SetupResult;
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ beforeEach(async () => {
+ onUpdate = jest.fn();
+
+ await act(async () => {
+ testBed = await setup({
+ value: {
+ processors: [],
+ },
+ onFlyoutOpen: jest.fn(),
+ onUpdate,
+ });
+ });
+ testBed.component.update();
+ const {
+ actions: { addProcessor, addProcessorType },
+ } = testBed;
+ // Open the processor flyout
+ addProcessor();
+
+ // Add type (the other fields are not visible until a type is selected)
+ await addProcessorType(CIRCLE_TYPE);
+ });
+
+ test('prevents form submission if required fields are not provided', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Click submit button with only the type defined
+ await saveNewProcessor();
+
+ // Expect form error as "field" and "shape_type" are required parameters
+ expect(form.getErrorsMessages()).toEqual([
+ 'A field value is required.',
+ 'A shape type value is required.',
+ ]);
+ });
+
+ test('saves with required parameter values', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Add "field" value (required)
+ form.setInputValue('fieldNameField.input', 'field_1');
+ // Save the field
+ form.setSelectValue('shapeSelectorField', 'shape');
+ // Set the error distance
+ form.setInputValue('errorDistanceField.input', '10');
+
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, CIRCLE_TYPE);
+
+ expect(processors[0].circle).toEqual({
+ field: 'field_1',
+ error_distance: 10,
+ shape_type: 'shape',
+ });
+ });
+
+ test('allows optional parameters to be set', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Add "field" value (required)
+ form.setInputValue('fieldNameField.input', 'field_1');
+ // Select the shape
+ form.setSelectValue('shapeSelectorField', 'geo_shape');
+ // Add "target_field" value
+ form.setInputValue('targetField.input', 'target_field');
+
+ form.setInputValue('errorDistanceField.input', '10');
+
+ // Save the field with new changes
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, CIRCLE_TYPE);
+ expect(processors[0].circle).toEqual({
+ field: 'field_1',
+ error_distance: 10,
+ shape_type: 'geo_shape',
+ target_field: 'target_field',
+ });
+ });
+});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
index c00f09b2d2b06..15e8c323b1308 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
@@ -151,6 +151,8 @@ type TestSubject =
| 'keepOriginalField.input'
| 'removeIfSuccessfulField.input'
| 'targetFieldsField.input'
+ | 'shapeSelectorField'
+ | 'errorDistanceField.input'
| 'separatorValueField.input'
| 'quoteValueField.input'
| 'emptyValueField.input'
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx
index acb480df6d35f..74a7f37d841ae 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/circle.tsx
@@ -97,6 +97,7 @@ export const Circle: FunctionComponent = () => {
/>
{
Date: Wed, 16 Jun 2021 12:42:15 -0400
Subject: [PATCH 33/98] Fix 7.13 aggregation reference issue (#102256)
---
docs/user/dashboard/aggregation-reference.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/user/dashboard/aggregation-reference.asciidoc b/docs/user/dashboard/aggregation-reference.asciidoc
index 001114578a1cd..cb5c484def3b9 100644
--- a/docs/user/dashboard/aggregation-reference.asciidoc
+++ b/docs/user/dashboard/aggregation-reference.asciidoc
@@ -190,8 +190,8 @@ For information about {es} metrics aggregations, refer to {ref}/search-aggregati
| Metrics with filters
|
-^| X
|
+^| X
|
| Average
From dbe3ca97082b62247098ec59a78cb129f05250c4 Mon Sep 17 00:00:00 2001
From: Nick Partridge
Date: Wed, 16 Jun 2021 12:05:56 -0500
Subject: [PATCH 34/98] Add auto-backport by default to ech renovate bot prs
(#102208)
---
renovate.json5 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/renovate.json5 b/renovate.json5
index f533eac479650..2a3b9d740ee93 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -39,7 +39,7 @@
packageNames: ['@elastic/charts'],
reviewers: ['markov00', 'nickofthyme'],
matchBaseBranches: ['master'],
- labels: ['release_note:skip', 'v8.0.0', 'v7.14.0'],
+ labels: ['release_note:skip', 'v8.0.0', 'v7.14.0', 'auto-backport'],
enabled: true,
},
{
From c8256d57bdf23de5354b178e418b8e6694851c64 Mon Sep 17 00:00:00 2001
From: spalger
Date: Wed, 16 Jun 2021 10:07:21 -0700
Subject: [PATCH 35/98] skip flaky suite (#101984)
---
x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
index 7d235d9e18108..bbd212b61e439 100644
--- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts
@@ -11,7 +11,8 @@ import { delay } from 'bluebird';
import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
- describe('uptime alerts', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/101984
+ describe.skip('uptime alerts', () => {
const pageObjects = getPageObjects(['common', 'uptime']);
const supertest = getService('supertest');
const retry = getService('retry');
From f4e0895b173faf6c0878b82182265697d79ab481 Mon Sep 17 00:00:00 2001
From: spalger
Date: Wed, 16 Jun 2021 10:10:38 -0700
Subject: [PATCH 36/98] skip flaky suite (#100296)
---
.../security_solution_endpoint/apps/endpoint/policy_details.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
index 44348d1ad0d9c..e01a24d2ea8d5 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
@@ -21,7 +21,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const policyTestResources = getService('policyTestResources');
- describe('When on the Endpoint Policy Details Page', function () {
+ // FLAKY: https://github.com/elastic/kibana/issues/100296
+ describe.skip('When on the Endpoint Policy Details Page', function () {
describe('with an invalid policy id', () => {
it('should display an error', async () => {
await pageObjects.policy.navigateToPolicyDetails('invalid-id');
From 3e723045a048ea692c3f1e01700096a3983291b1 Mon Sep 17 00:00:00 2001
From: spalger
Date: Wed, 16 Jun 2021 10:14:04 -0700
Subject: [PATCH 37/98] remove nested skip (#100296)
---
.../security_solution_endpoint/apps/endpoint/policy_details.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
index e01a24d2ea8d5..ae60935013d27 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts
@@ -757,8 +757,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
});
- // FLAKY: https://github.com/elastic/kibana/issues/100296
- describe.skip('when on Ingest Policy Edit Package Policy page', async () => {
+ describe('when on Ingest Policy Edit Package Policy page', async () => {
let policyInfo: PolicyTestResourceInfo;
beforeEach(async () => {
// Create a policy and navigate to Ingest app
From 4a941565502547f96bab72786e1ac11f61f19558 Mon Sep 17 00:00:00 2001
From: Kyle Pollich
Date: Wed, 16 Jun 2021 13:29:38 -0400
Subject: [PATCH 38/98] [Fleet + Integrations UI] Migrate Fleet UI to new
tabbed layout (#101828)
* WIP: Migrate fleet to new page layout system
* Add 'Add Agent' button to agents table
* Fix flyout import in search and filter bar
* Place settings/feedback in header
* Move actions to top nav
* Fix i18n + types + unit test failures
* Remove unused props in DefaultLayout
* Fix background height in Fleet layout
This is fixed through a hack for now, because Kibana's layout doesn't
allow apps to flex the top-level wrapper via `flex: 1`. The same
behavior reported in the original issue (#101781) is present in all
other Kibana apps.
Fixes #101781
* Use euiHeaderHeightCompensation for min-height calc
* Move settings portal to app component
* Fix agent details URL in failing unit test
* Remove unreferenced overview files + update functional tests
* Remove unneeded fragment
* Remove beta badges in Fleet + Integrations
Fixes #100731
* Fix i18n
* Fix page path reference
* Fix failing tests
* Re-fix i18n post merge
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/fleet/kibana.json | 10 +-
.../fleet/public/applications/fleet/app.tsx | 129 +++++++----
.../fleet/hooks/use_breadcrumbs.tsx | 33 +--
.../fleet/public/applications/fleet/index.tsx | 7 +-
.../applications/fleet/layouts/default.tsx | 207 +++++++-----------
.../fleet/sections/agent_policy/index.tsx | 6 +-
.../sections/agent_policy/list_page/index.tsx | 37 +---
.../agent_details_integrations.tsx | 2 +-
.../agents/agent_details_page/index.tsx | 17 +-
.../components/search_and_filter_bar.tsx | 24 +-
.../sections/agents/agent_list_page/index.tsx | 6 +-
.../agents/components/list_layout.tsx | 101 ---------
.../enrollment_token_list_page/index.tsx | 7 +-
.../fleet/sections/agents/index.tsx | 28 +--
.../fleet/sections/data_stream/index.tsx | 5 +-
.../sections/data_stream/list_page/index.tsx | 207 ++++++++----------
.../applications/fleet/sections/index.tsx | 5 +-
.../components/agent_policy_section.tsx | 78 -------
.../overview/components/agent_section.tsx | 87 --------
.../components/datastream_section.tsx | 99 ---------
.../components/integration_section.tsx | 88 --------
.../overview/components/overview_panel.tsx | 74 -------
.../overview/components/overview_stats.tsx | 24 --
.../fleet/sections/overview/index.tsx | 110 ----------
.../integrations/layouts/default.tsx | 11 +-
.../managed_instructions.tsx | 2 +-
.../public/components/linked_agent_count.tsx | 2 +-
.../fleet/public/constants/page_paths.ts | 38 ++--
.../fleet/public/layouts/without_header.tsx | 7 +
.../fleet/public/mock/plugin_dependencies.ts | 2 +
x-pack/plugins/fleet/public/plugin.ts | 3 +
.../action_results/action_results_summary.tsx | 2 +-
.../osquery/public/results/results_table.tsx | 2 +-
.../view/hooks/use_endpoint_action_items.tsx | 8 +-
.../pages/endpoint_hosts/view/index.test.tsx | 4 +-
.../pages/endpoint_hosts/view/index.tsx | 4 +-
.../endpoint/routes/actions/isolation.ts | 4 +-
.../translations/translations/ja-JP.json | 38 ----
.../translations/translations/zh-CN.json | 38 ----
.../apps/fleet/agents_page.ts | 38 ++++
.../test/fleet_functional/apps/fleet/index.ts | 2 +-
.../apps/fleet/overview_page.ts | 38 ----
.../{overview_page.ts => agents_page.ts} | 23 +-
.../fleet_functional/page_objects/index.ts | 4 +-
44 files changed, 421 insertions(+), 1240 deletions(-)
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx
create mode 100644 x-pack/test/fleet_functional/apps/fleet/agents_page.ts
delete mode 100644 x-pack/test/fleet_functional/apps/fleet/overview_page.ts
rename x-pack/test/fleet_functional/page_objects/{overview_page.ts => agents_page.ts} (55%)
diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json
index 4a4019e3e9e47..ca1407be2008a 100644
--- a/x-pack/plugins/fleet/kibana.json
+++ b/x-pack/plugins/fleet/kibana.json
@@ -4,14 +4,8 @@
"server": true,
"ui": true,
"configPath": ["xpack", "fleet"],
- "requiredPlugins": ["licensing", "data", "encryptedSavedObjects"],
- "optionalPlugins": [
- "security",
- "features",
- "cloud",
- "usageCollection",
- "home"
- ],
+ "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation"],
+ "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home"],
"extraPublicDirs": ["common"],
"requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils"]
}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx
index 1398e121c6870..1072a6b66419e 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx
@@ -7,7 +7,7 @@
import React, { memo, useEffect, useState } from 'react';
import type { AppMountParameters } from 'kibana/public';
-import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel } from '@elastic/eui';
+import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel, EuiPortal } from '@elastic/eui';
import type { History } from 'history';
import { createHashHistory } from 'history';
import { Router, Redirect, Route, Switch } from 'react-router-dom';
@@ -16,11 +16,13 @@ import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import useObservable from 'react-use/lib/useObservable';
+import type { TopNavMenuData } from 'src/plugins/navigation/public';
+
import type { FleetConfigType, FleetStartServices } from '../../plugin';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common';
-import { PackageInstallProvider } from '../integrations/hooks';
+import { PackageInstallProvider, useUrlModal } from '../integrations/hooks';
import {
ConfigContext,
@@ -30,25 +32,25 @@ import {
sendGetPermissionsCheck,
sendSetup,
useBreadcrumbs,
- useConfig,
useStartServices,
UIExtensionsContext,
} from './hooks';
-import { Error, Loading } from './components';
+import { Error, Loading, SettingFlyout } from './components';
import type { UIExtensionsStorage } from './types';
import { FLEET_ROUTING_PATHS } from './constants';
import { DefaultLayout, WithoutHeaderLayout } from './layouts';
import { AgentPolicyApp } from './sections/agent_policy';
import { DataStreamApp } from './sections/data_stream';
-import { FleetApp } from './sections/agents';
-import { IngestManagerOverview } from './sections/overview';
-import { ProtectedRoute } from './index';
+import { AgentsApp } from './sections/agents';
import { CreatePackagePolicyPage } from './sections/agent_policy/create_package_policy_page';
+import { EnrollmentTokenListPage } from './sections/agents/enrollment_token_list_page';
+
+const FEEDBACK_URL = 'https://ela.st/fleet-feedback';
const ErrorLayout = ({ children }: { children: JSX.Element }) => (
-
+
{children}
@@ -233,37 +235,82 @@ export const FleetAppContext: React.FC<{
}
);
-export const AppRoutes = memo(() => {
- const { agents } = useConfig();
+const FleetTopNav = memo(
+ ({ setHeaderActionMenu }: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'] }) => {
+ const { getModalHref } = useUrlModal();
+ const services = useStartServices();
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
+ const { TopNavMenu } = services.navigation.ui;
+
+ const topNavConfig: TopNavMenuData[] = [
+ {
+ label: i18n.translate('xpack.fleet.appNavigation.sendFeedbackButton', {
+ defaultMessage: 'Send Feedback',
+ }),
+ iconType: 'popout',
+ run: () => window.open(FEEDBACK_URL),
+ },
+
+ {
+ label: i18n.translate('xpack.fleet.appNavigation.settingsButton', {
+ defaultMessage: 'Fleet settings',
+ }),
+ iconType: 'gear',
+ run: () => (window.location.href = getModalHref('settings')),
+ },
+ ];
+ return (
+
+ );
+ }
+);
+
+export const AppRoutes = memo(
+ ({ setHeaderActionMenu }: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'] }) => {
+ const { modal, setModal } = useUrlModal();
+
+ return (
+ <>
+
+
+ {modal === 'settings' && (
+
+ {
+ setModal(null);
+ }}
+ />
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* TODO: Move this route to the Integrations app */}
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+);
diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx
index fd980475dc919..254885ea71b1e 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx
@@ -20,7 +20,7 @@ interface AdditionalBreadcrumbOptions {
type Breadcrumb = ChromeBreadcrumb & Partial;
const BASE_BREADCRUMB: Breadcrumb = {
- href: pagePathGetters.overview()[1],
+ href: pagePathGetters.base()[1],
text: i18n.translate('xpack.fleet.breadcrumbs.appTitle', {
defaultMessage: 'Fleet',
}),
@@ -38,15 +38,6 @@ const breadcrumbGetters: {
[key in Page]?: (values: DynamicPagePathValues) => Breadcrumb[];
} = {
base: () => [BASE_BREADCRUMB],
- overview: () => [
- BASE_BREADCRUMB,
- {
- text: i18n.translate('xpack.fleet.breadcrumbs.overviewPageTitle', {
- defaultMessage: 'Overview',
- }),
- },
- ],
-
policies: () => [
BASE_BREADCRUMB,
{
@@ -122,15 +113,7 @@ const breadcrumbGetters: {
}),
},
],
- fleet: () => [
- BASE_BREADCRUMB,
- {
- text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', {
- defaultMessage: 'Agents',
- }),
- },
- ],
- fleet_agent_list: () => [
+ agent_list: () => [
BASE_BREADCRUMB,
{
text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', {
@@ -138,24 +121,18 @@ const breadcrumbGetters: {
}),
},
],
- fleet_agent_details: ({ agentHost }) => [
+ agent_details: ({ agentHost }) => [
BASE_BREADCRUMB,
{
- href: pagePathGetters.fleet()[1],
+ href: pagePathGetters.agent_list({})[1],
text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', {
defaultMessage: 'Agents',
}),
},
{ text: agentHost },
],
- fleet_enrollment_tokens: () => [
+ enrollment_tokens: () => [
BASE_BREADCRUMB,
- {
- href: pagePathGetters.fleet()[1],
- text: i18n.translate('xpack.fleet.breadcrumbs.agentsPageTitle', {
- defaultMessage: 'Agents',
- }),
- },
{
text: i18n.translate('xpack.fleet.breadcrumbs.enrollmentTokensPageTitle', {
defaultMessage: 'Enrollment tokens',
diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx
index 7d31fb31b36a4..8942c13a0a69d 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx
@@ -37,6 +37,7 @@ interface FleetAppProps {
history: AppMountParameters['history'];
kibanaVersion: string;
extensions: UIExtensionsStorage;
+ setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
}
const FleetApp = ({
basepath,
@@ -45,6 +46,7 @@ const FleetApp = ({
history,
kibanaVersion,
extensions,
+ setHeaderActionMenu,
}: FleetAppProps) => {
return (
-
+
);
@@ -64,7 +66,7 @@ const FleetApp = ({
export function renderApp(
startServices: FleetStartServices,
- { element, appBasePath, history }: AppMountParameters,
+ { element, appBasePath, history, setHeaderActionMenu }: AppMountParameters,
config: FleetConfigType,
kibanaVersion: string,
extensions: UIExtensionsStorage
@@ -77,6 +79,7 @@ export function renderApp(
history={history}
kibanaVersion={kibanaVersion}
extensions={extensions}
+ setHeaderActionMenu={setHeaderActionMenu}
/>,
element
);
diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
index d707fd162ae02..f312ff374d792 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
@@ -6,145 +6,98 @@
*/
import React from 'react';
-import styled from 'styled-components';
-import {
- EuiTabs,
- EuiTab,
- EuiFlexGroup,
- EuiFlexItem,
- EuiButtonEmpty,
- EuiPortal,
-} from '@elastic/eui';
+import { EuiText, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import type { Section } from '../sections';
-import { SettingFlyout } from '../components';
-import { useLink, useConfig, useUrlModal } from '../hooks';
+import { useLink, useConfig } from '../hooks';
+import { WithHeaderLayout } from '../../../layouts';
interface Props {
- showNav?: boolean;
- showSettings?: boolean;
section?: Section;
children?: React.ReactNode;
}
-const Container = styled.div`
- min-height: calc(
- 100vh - ${(props) => parseFloat(props.theme.eui.euiHeaderHeightCompensation) * 2}px
- );
- background: ${(props) => props.theme.eui.euiColorEmptyShade};
- display: flex;
- flex-direction: column;
-`;
-
-const Wrapper = styled.div`
- display: flex;
- flex-direction: column;
- flex: 1;
-`;
-
-const Nav = styled.nav`
- background: ${(props) => props.theme.eui.euiColorEmptyShade};
- border-bottom: ${(props) => props.theme.eui.euiBorderThin};
- padding: ${(props) =>
- `${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL} ${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL}`};
- .euiTabs {
- padding-left: 3px;
- margin-left: -3px;
- }
-`;
-
-export const DefaultLayout: React.FunctionComponent = ({
- showNav = true,
- showSettings = true,
- section,
- children,
-}) => {
+export const DefaultLayout: React.FunctionComponent = ({ section, children }) => {
const { getHref } = useLink();
const { agents } = useConfig();
- const { modal, setModal, getModalHref } = useUrlModal();
return (
- <>
- {modal === 'settings' && (
-
- {
- setModal(null);
- }}
- />
-
- )}
-
-
-
- {showNav ? (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {showSettings ? (
-
-
-
-
-
- ) : null}
-
-
-
-
- ) : null}
- {children}
-
-
- >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ tabs={[
+ {
+ name: (
+
+ ),
+ isSelected: section === 'agents',
+ href: getHref('agent_list'),
+ disabled: !agents?.enabled,
+ 'data-test-subj': 'fleet-agents-tab',
+ },
+ {
+ name: (
+
+ ),
+ isSelected: section === 'agent_policies',
+ href: getHref('policies_list'),
+ 'data-test-subj': 'fleet-agent-policies-tab',
+ },
+ {
+ name: (
+
+ ),
+ isSelected: section === 'enrollment_tokens',
+ href: getHref('enrollment_tokens'),
+ 'data-test-subj': 'fleet-enrollment-tokens-tab',
+ },
+ {
+ name: (
+
+ ),
+ isSelected: section === 'data_streams',
+ href: getHref('data_streams'),
+ 'data-test-subj': 'fleet-datastreams-tab',
+ },
+ ]}
+ >
+ {children}
+
);
};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx
index c0ec811ce2bcd..d8db44e28e4af 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx
@@ -11,6 +11,8 @@ import { HashRouter as Router, Switch, Route } from 'react-router-dom';
import { FLEET_ROUTING_PATHS } from '../../constants';
import { useBreadcrumbs } from '../../hooks';
+import { DefaultLayout } from '../../layouts';
+
import { AgentPolicyListPage } from './list_page';
import { AgentPolicyDetailsPage } from './details_page';
import { CreatePackagePolicyPage } from './create_package_policy_page';
@@ -32,7 +34,9 @@ export const AgentPolicyApp: React.FunctionComponent = () => {
-
+
+
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx
index 48b9118d11566..10859e32f0080 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx
@@ -9,7 +9,6 @@ import React, { useCallback, useMemo, useState } from 'react';
import type { EuiTableActionsColumnType, EuiTableFieldDataColumnType } from '@elastic/eui';
import {
EuiSpacer,
- EuiText,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
@@ -25,7 +24,6 @@ import { useHistory } from 'react-router-dom';
import type { AgentPolicy } from '../../../types';
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../constants';
-import { WithHeaderLayout } from '../../../layouts';
import {
useCapabilities,
useGetAgentPolicies,
@@ -41,37 +39,6 @@ import { LinkedAgentCount, AgentPolicyActionMenu } from '../components';
import { CreateAgentPolicyFlyout } from './components';
-const AgentPolicyListPageLayout: React.FunctionComponent = ({ children }) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
- >
- {children}
-
-);
-
export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
useBreadcrumbs('policies_list');
const { getPath } = useLink();
@@ -246,7 +213,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
};
return (
-
+ <>
{isCreateAgentPolicyFlyoutOpen ? (
{
@@ -322,6 +289,6 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
sorting={{ sort: sorting }}
onChange={onTableChange}
/>
-
+ >
);
};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
index 6e0206603a458..a599d726cedef 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
@@ -101,7 +101,7 @@ export const AgentDetailsIntegration: React.FunctionComponent<{
})}
>
{
() => (
-
+
{
name: i18n.translate('xpack.fleet.agentDetails.subTabs.detailsTab', {
defaultMessage: 'Agent details',
}),
- href: getHref('fleet_agent_details', { agentId, tabId: 'details' }),
+ href: getHref('agent_details', { agentId, tabId: 'details' }),
isSelected: !tabId || tabId === 'details',
},
{
@@ -240,7 +235,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
name: i18n.translate('xpack.fleet.agentDetails.subTabs.logsTab', {
defaultMessage: 'Logs',
}),
- href: getHref('fleet_agent_details', { agentId, tabId: 'logs' }),
+ href: getHref('agent_details_logs', { agentId, tabId: 'logs' }),
isSelected: tabId === 'logs',
},
];
@@ -299,7 +294,7 @@ const AgentDetailsPageContent: React.FunctionComponent<{
agent: Agent;
agentPolicy?: AgentPolicy;
}> = ({ agent, agentPolicy }) => {
- useBreadcrumbs('fleet_agent_details', {
+ useBreadcrumbs('agent_list', {
agentHost:
typeof agent.local_metadata.host === 'object' &&
typeof agent.local_metadata.host.hostname === 'string'
@@ -309,13 +304,13 @@ const AgentDetailsPageContent: React.FunctionComponent<{
return (
{
return ;
}}
/>
{
return ;
}}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx
index 1beaf437ceb0e..1d7b44ceefb7c 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx
@@ -7,18 +7,20 @@
import React, { useState } from 'react';
import {
+ EuiButton,
EuiFilterButton,
EuiFilterGroup,
EuiFilterSelectItem,
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
+ EuiPortal,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import type { AgentPolicy } from '../../../../types';
-import { SearchBar } from '../../../../components';
+import { AgentEnrollmentFlyout, SearchBar } from '../../../../components';
import { AGENTS_INDEX } from '../../../../constants';
const statusFilters = [
@@ -77,6 +79,8 @@ export const SearchAndFilterBar: React.FunctionComponent<{
showUpgradeable,
onShowUpgradeableChange,
}) => {
+ const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
+
// Policies state for filtering
const [isAgentPoliciesFilterOpen, setIsAgentPoliciesFilterOpen] = useState(false);
@@ -97,6 +101,15 @@ export const SearchAndFilterBar: React.FunctionComponent<{
return (
<>
+ {isEnrollmentFlyoutOpen ? (
+
+ setIsEnrollmentFlyoutOpen(false)}
+ />
+
+ ) : null}
+
{/* Search and filter bar */}
@@ -207,6 +220,15 @@ export const SearchAndFilterBar: React.FunctionComponent<{
+
+ setIsEnrollmentFlyoutOpen(true)}
+ >
+
+
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx
index 672b8718c9cbe..431c4da3efb5b 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx
@@ -73,7 +73,7 @@ const RowActions = React.memo<{
const menuItems = [
@@ -146,7 +146,7 @@ function safeMetadata(val: any) {
export const AgentListPage: React.FunctionComponent<{}> = () => {
const { notifications } = useStartServices();
- useBreadcrumbs('fleet_agent_list');
+ useBreadcrumbs('agent_list');
const { getHref } = useLink();
const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || '';
const hasWriteCapabilites = useCapabilities().write;
@@ -358,7 +358,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
defaultMessage: 'Host',
}),
render: (host: string, agent: Agent) => (
-
+
{safeMetadata(host)}
),
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx
deleted file mode 100644
index 67758282521b7..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButton, EuiPortal } from '@elastic/eui';
-import type { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab';
-import { useRouteMatch } from 'react-router-dom';
-
-import { FLEET_ROUTING_PATHS } from '../../../constants';
-import { WithHeaderLayout } from '../../../layouts';
-import { useCapabilities, useLink, useGetAgentPolicies } from '../../../hooks';
-import { AgentEnrollmentFlyout } from '../../../components';
-
-export const ListLayout: React.FunctionComponent<{}> = ({ children }) => {
- const { getHref } = useLink();
- const hasWriteCapabilites = useCapabilities().write;
-
- // Agent enrollment flyout state
- const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = React.useState(false);
-
- const headerRightColumn = hasWriteCapabilites ? (
-
-
- setIsEnrollmentFlyoutOpen(true)}>
-
-
-
-
- ) : undefined;
- const headerLeftColumn = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-
- const agentPoliciesRequest = useGetAgentPolicies({
- page: 1,
- perPage: 1000,
- });
-
- const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : [];
-
- const routeMatch = useRouteMatch();
-
- return (
- ,
- isSelected: routeMatch.path === FLEET_ROUTING_PATHS.fleet_agent_list,
- href: getHref('fleet_agent_list'),
- },
- {
- name: (
-
- ),
- isSelected: routeMatch.path === FLEET_ROUTING_PATHS.fleet_enrollment_tokens,
- href: getHref('fleet_enrollment_tokens'),
- },
- ] as unknown) as EuiTabProps[]
- }
- >
- {isEnrollmentFlyoutOpen ? (
-
- setIsEnrollmentFlyoutOpen(false)}
- />
-
- ) : null}
- {children}
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx
index 8dc9ad33962e0..666d0887fe510 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx
@@ -34,6 +34,7 @@ import {
} from '../../../hooks';
import type { EnrollmentAPIKey, GetAgentPoliciesResponseItem } from '../../../types';
import { SearchBar } from '../../../components/search_bar';
+import { DefaultLayout } from '../../../layouts';
import { ConfirmEnrollmentTokenDelete } from './components/confirm_delete_modal';
@@ -155,7 +156,7 @@ const DeleteButton: React.FunctionComponent<{ apiKey: EnrollmentAPIKey; refresh:
};
export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => {
- useBreadcrumbs('fleet_enrollment_tokens');
+ useBreadcrumbs('enrollment_tokens');
const [isModalOpen, setModalOpen] = useState(false);
const [search, setSearch] = useState('');
const { pagination, setPagination, pageSizeOptions } = usePagination();
@@ -269,7 +270,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => {
];
return (
- <>
+
{isModalOpen && (
= () => {
setPagination(newPagination);
}}
/>
- >
+
);
};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx
index dcb33e7662dc4..79b19b443cca1 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx
@@ -7,7 +7,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
+import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import { FLEET_ROUTING_PATHS } from '../../constants';
import { Loading, Error } from '../../components';
@@ -18,20 +18,18 @@ import {
useCapabilities,
useGetSettings,
} from '../../hooks';
-import { WithoutHeaderLayout } from '../../layouts';
+import { DefaultLayout, WithoutHeaderLayout } from '../../layouts';
import { AgentListPage } from './agent_list_page';
import { FleetServerRequirementPage, MissingESRequirementsPage } from './agent_requirements_page';
import { AgentDetailsPage } from './agent_details_page';
import { NoAccessPage } from './error_pages/no_access';
-import { EnrollmentTokenListPage } from './enrollment_token_list_page';
-import { ListLayout } from './components/list_layout';
import { FleetServerUpgradeModal } from './components/fleet_server_upgrade_modal';
const REFRESH_INTERVAL_MS = 30000;
-export const FleetApp: React.FunctionComponent = () => {
- useBreadcrumbs('fleet');
+export const AgentsApp: React.FunctionComponent = () => {
+ useBreadcrumbs('agent_list');
const { agents } = useConfig();
const capabilities = useCapabilities();
@@ -110,16 +108,11 @@ export const FleetApp: React.FunctionComponent = () => {
return (
- }
- />
-
+
-
-
+
+
{fleetServerModalVisible && (
)}
@@ -128,12 +121,7 @@ export const FleetApp: React.FunctionComponent = () => {
) : (
)}
-
-
-
-
-
-
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx
index bc3a0229284db..c660d3ed29767 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx
@@ -9,6 +9,7 @@ import React from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import { FLEET_ROUTING_PATHS } from '../../constants';
+import { DefaultLayout } from '../../layouts';
import { DataStreamListPage } from './list_page';
@@ -17,7 +18,9 @@ export const DataStreamApp: React.FunctionComponent = () => {
-
+
+
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx
index e805fb8f6f64e..ac236578e6f58 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx
@@ -10,7 +10,6 @@ import type { EuiTableActionsColumnType, EuiTableFieldDataColumnType } from '@el
import {
EuiBadge,
EuiButton,
- EuiText,
EuiFlexGroup,
EuiFlexItem,
EuiEmptyPrompt,
@@ -20,43 +19,11 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage, FormattedDate } from '@kbn/i18n/react';
import type { DataStream } from '../../../types';
-import { WithHeaderLayout } from '../../../layouts';
import { useGetDataStreams, useStartServices, usePagination, useBreadcrumbs } from '../../../hooks';
import { PackageIcon } from '../../../components';
import { DataStreamRowActions } from './components/data_stream_row_actions';
-const DataStreamListPageLayout: React.FunctionComponent = ({ children }) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
- >
- {children}
-
-);
-
export const DataStreamListPage: React.FunctionComponent<{}> = () => {
useBreadcrumbs('data_streams');
@@ -232,97 +199,95 @@ export const DataStreamListPage: React.FunctionComponent<{}> = () => {
}
return (
-
-
- ) : dataStreamsData && !dataStreamsData.data_streams.length ? (
- emptyPrompt
- ) : (
+
+ ) : dataStreamsData && !dataStreamsData.data_streams.length ? (
+ emptyPrompt
+ ) : (
+
+ )
+ }
+ items={dataStreamsData ? dataStreamsData.data_streams : []}
+ itemId="index"
+ columns={columns}
+ pagination={{
+ initialPageSize: pagination.pageSize,
+ pageSizeOptions,
+ }}
+ sorting={true}
+ search={{
+ toolsRight: [
+ resendRequest()}
+ >
- )
- }
- items={dataStreamsData ? dataStreamsData.data_streams : []}
- itemId="index"
- columns={columns}
- pagination={{
- initialPageSize: pagination.pageSize,
- pageSizeOptions,
- }}
- sorting={true}
- search={{
- toolsRight: [
- resendRequest()}
- >
-
- ,
- ],
- box: {
- placeholder: i18n.translate('xpack.fleet.dataStreamList.searchPlaceholderTitle', {
- defaultMessage: 'Filter data streams',
+ ,
+ ],
+ box: {
+ placeholder: i18n.translate('xpack.fleet.dataStreamList.searchPlaceholderTitle', {
+ defaultMessage: 'Filter data streams',
+ }),
+ incremental: true,
+ },
+ filters: [
+ {
+ type: 'field_value_selection',
+ field: 'dataset',
+ name: i18n.translate('xpack.fleet.dataStreamList.datasetColumnTitle', {
+ defaultMessage: 'Dataset',
}),
- incremental: true,
+ multiSelect: 'or',
+ operator: 'exact',
+ options: filterOptions.dataset,
},
- filters: [
- {
- type: 'field_value_selection',
- field: 'dataset',
- name: i18n.translate('xpack.fleet.dataStreamList.datasetColumnTitle', {
- defaultMessage: 'Dataset',
- }),
- multiSelect: 'or',
- operator: 'exact',
- options: filterOptions.dataset,
- },
- {
- type: 'field_value_selection',
- field: 'type',
- name: i18n.translate('xpack.fleet.dataStreamList.typeColumnTitle', {
- defaultMessage: 'Type',
- }),
- multiSelect: 'or',
- operator: 'exact',
- options: filterOptions.type,
- },
- {
- type: 'field_value_selection',
- field: 'namespace',
- name: i18n.translate('xpack.fleet.dataStreamList.namespaceColumnTitle', {
- defaultMessage: 'Namespace',
- }),
- multiSelect: 'or',
- operator: 'exact',
- options: filterOptions.namespace,
- },
- {
- type: 'field_value_selection',
- field: 'package',
- name: i18n.translate('xpack.fleet.dataStreamList.integrationColumnTitle', {
- defaultMessage: 'Integration',
- }),
- multiSelect: 'or',
- operator: 'exact',
- options: filterOptions.package,
- },
- ],
- }}
- />
-
+ {
+ type: 'field_value_selection',
+ field: 'type',
+ name: i18n.translate('xpack.fleet.dataStreamList.typeColumnTitle', {
+ defaultMessage: 'Type',
+ }),
+ multiSelect: 'or',
+ operator: 'exact',
+ options: filterOptions.type,
+ },
+ {
+ type: 'field_value_selection',
+ field: 'namespace',
+ name: i18n.translate('xpack.fleet.dataStreamList.namespaceColumnTitle', {
+ defaultMessage: 'Namespace',
+ }),
+ multiSelect: 'or',
+ operator: 'exact',
+ options: filterOptions.namespace,
+ },
+ {
+ type: 'field_value_selection',
+ field: 'package',
+ name: i18n.translate('xpack.fleet.dataStreamList.integrationColumnTitle', {
+ defaultMessage: 'Integration',
+ }),
+ multiSelect: 'or',
+ operator: 'exact',
+ options: filterOptions.package,
+ },
+ ],
+ }}
+ />
);
};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx
index 810334e2df9ce..b36fbf4bb815e 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx
@@ -5,9 +5,8 @@
* 2.0.
*/
-export { IngestManagerOverview } from './overview';
export { AgentPolicyApp } from './agent_policy';
export { DataStreamApp } from './data_stream';
-export { FleetApp } from './agents';
+export { AgentsApp } from './agents';
-export type Section = 'overview' | 'agent_policy' | 'fleet' | 'data_stream';
+export type Section = 'agents' | 'agent_policies' | 'enrollment_tokens' | 'data_streams';
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx
deleted file mode 100644
index 79a4f08faa752..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-import {
- EuiFlexItem,
- EuiI18nNumber,
- EuiDescriptionListTitle,
- EuiDescriptionListDescription,
-} from '@elastic/eui';
-
-import { SO_SEARCH_LIMIT } from '../../../constants';
-import { useLink, useGetPackagePolicies } from '../../../hooks';
-import type { AgentPolicy } from '../../../types';
-import { Loading } from '../../agents/components';
-
-import { OverviewStats } from './overview_stats';
-import { OverviewPanel } from './overview_panel';
-
-export const OverviewPolicySection: React.FC<{ agentPolicies: AgentPolicy[] }> = ({
- agentPolicies,
-}) => {
- const { getHref } = useLink();
- const packagePoliciesRequest = useGetPackagePolicies({
- page: 1,
- perPage: SO_SEARCH_LIMIT,
- });
-
- return (
-
-
-
- {packagePoliciesRequest.isLoading ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx
deleted file mode 100644
index d69306969c78c..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-import {
- EuiI18nNumber,
- EuiDescriptionListTitle,
- EuiDescriptionListDescription,
- EuiFlexItem,
-} from '@elastic/eui';
-
-import { useLink, useGetAgentStatus } from '../../../hooks';
-import { Loading } from '../../agents/components';
-
-import { OverviewPanel } from './overview_panel';
-import { OverviewStats } from './overview_stats';
-
-export const OverviewAgentSection = () => {
- const { getHref } = useLink();
- const agentStatusRequest = useGetAgentStatus({});
-
- return (
-
-
-
- {agentStatusRequest.isLoading ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx
deleted file mode 100644
index b51be3fdd20e5..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-import {
- EuiFlexItem,
- EuiI18nNumber,
- EuiDescriptionListTitle,
- EuiDescriptionListDescription,
-} from '@elastic/eui';
-
-import { useLink, useGetDataStreams, useStartServices } from '../../../hooks';
-import { Loading } from '../../agents/components';
-
-import { OverviewPanel } from './overview_panel';
-import { OverviewStats } from './overview_stats';
-
-export const OverviewDatastreamSection: React.FC = () => {
- const { getHref } = useLink();
- const datastreamRequest = useGetDataStreams();
- const {
- data: { fieldFormats },
- } = useStartServices();
-
- const total = datastreamRequest.data?.data_streams?.length ?? 0;
- let sizeBytes = 0;
- const namespaces = new Set();
- if (datastreamRequest.data) {
- datastreamRequest.data.data_streams.forEach((val) => {
- namespaces.add(val.namespace);
- sizeBytes += val.size_in_bytes;
- });
- }
-
- let size: string;
- try {
- const formatter = fieldFormats.getInstance('bytes');
- size = formatter.convert(sizeBytes);
- } catch (e) {
- size = `${sizeBytes}b`;
- }
-
- return (
-
-
-
- {datastreamRequest.isLoading ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {size}
- >
- )}
-
-
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx
deleted file mode 100644
index 5ada8e298507c..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-import {
- EuiFlexItem,
- EuiI18nNumber,
- EuiDescriptionListTitle,
- EuiDescriptionListDescription,
-} from '@elastic/eui';
-
-import { useLink, useGetPackages } from '../../../hooks';
-import { Loading } from '../../agents/components';
-import { installationStatuses } from '../../../../../../common/constants';
-
-import { OverviewStats } from './overview_stats';
-import { OverviewPanel } from './overview_panel';
-
-export const OverviewIntegrationSection: React.FC = () => {
- const { getHref } = useLink();
- const packagesRequest = useGetPackages();
- const res = packagesRequest.data?.response;
- const total = res?.length ?? 0;
- const installed = res?.filter((p) => p.status === installationStatuses.Installed)?.length ?? 0;
- const updatablePackages =
- res?.filter(
- (item) => 'savedObject' in item && item.version > item.savedObject.attributes.version
- )?.length ?? 0;
- return (
-
-
-
- {packagesRequest.isLoading ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx
deleted file mode 100644
index c402bc15f7b02..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import styled from 'styled-components';
-import {
- EuiPanel,
- EuiFlexGroup,
- EuiFlexItem,
- EuiTitle,
- EuiIconTip,
- EuiButtonEmpty,
-} from '@elastic/eui';
-
-const StyledPanel = styled(EuiPanel).attrs((props) => ({
- paddingSize: 'm',
-}))`
- header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- border-bottom: 1px solid ${(props) => props.theme.eui.euiColorLightShade};
- margin: -${(props) => props.theme.eui.paddingSizes.m} -${(props) =>
- props.theme.eui.paddingSizes.m}
- ${(props) => props.theme.eui.paddingSizes.m};
- padding: ${(props) => props.theme.eui.paddingSizes.s}
- ${(props) => props.theme.eui.paddingSizes.m};
- }
-
- h2 {
- padding: ${(props) => props.theme.eui.paddingSizes.xs} 0;
- }
-`;
-
-interface OverviewPanelProps {
- title: string;
- tooltip: string;
- linkToText: string;
- linkTo: string;
- children: React.ReactNode;
-}
-
-export const OverviewPanel = ({
- title,
- tooltip,
- linkToText,
- linkTo,
- children,
-}: OverviewPanelProps) => {
- return (
-
-
-
-
-
- {title}
-
-
-
-
-
-
-
- {linkToText}
-
-
- {children}
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx
deleted file mode 100644
index acb94e4b05695..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import styled from 'styled-components';
-import { EuiDescriptionList } from '@elastic/eui';
-
-export const OverviewStats = styled(EuiDescriptionList).attrs((props) => ({
- compressed: true,
- textStyle: 'reverse',
- type: 'column',
-}))`
- & > * {
- margin-top: ${(props) => props.theme.eui.paddingSizes.s} !important;
-
- &:first-child,
- &:nth-child(2) {
- margin-top: 0 !important;
- }
- }
-`;
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx
deleted file mode 100644
index f905fd1c89da2..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useState } from 'react';
-import {
- EuiButton,
- EuiBetaBadge,
- EuiText,
- EuiTitle,
- EuiFlexGrid,
- EuiFlexGroup,
- EuiFlexItem,
-} from '@elastic/eui';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-
-import { WithHeaderLayout } from '../../layouts';
-import { useGetAgentPolicies, useBreadcrumbs } from '../../hooks';
-import { AgentEnrollmentFlyout } from '../../components';
-
-import { OverviewAgentSection } from './components/agent_section';
-import { OverviewPolicySection } from './components/agent_policy_section';
-import { OverviewIntegrationSection } from './components/integration_section';
-import { OverviewDatastreamSection } from './components/datastream_section';
-
-export const IngestManagerOverview: React.FunctionComponent = () => {
- useBreadcrumbs('overview');
-
- // Agent enrollment flyout state
- const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
-
- // Agent policies required for enrollment flyout
- const agentPoliciesRequest = useGetAgentPolicies({
- page: 1,
- perPage: 1000,
- });
- const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : [];
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
- rightColumn={
-
-
- setIsEnrollmentFlyoutOpen(true)}>
-
-
-
-
- }
- >
- {isEnrollmentFlyoutOpen && (
- setIsEnrollmentFlyoutOpen(false)}
- />
- )}
-
-
-
-
-
-
-
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx
index 4c1ff4972b89e..98b8e9515e689 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
import React, { memo } from 'react';
-import { EuiText, EuiBetaBadge } from '@elastic/eui';
+import { EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useLink } from '../../../hooks';
@@ -30,15 +30,6 @@ export const DefaultLayout: React.FunctionComponent = memo(({ section, ch
{' '}
- }
- tooltipContent={
-
- }
- />
}
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
index 2bb8586a11503..e7045173f1257 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/managed_instructions.tsx
@@ -36,7 +36,7 @@ const DefaultMissingRequirements = () => {
defaultMessage="Before enrolling agents, {link}."
values={{
link: (
-
+
0 ? (
diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts
index 3c9c0e5759615..326cfd804bd57 100644
--- a/x-pack/plugins/fleet/public/constants/page_paths.ts
+++ b/x-pack/plugins/fleet/public/constants/page_paths.ts
@@ -13,8 +13,7 @@ export type StaticPage =
| 'integrations_installed'
| 'policies'
| 'policies_list'
- | 'fleet'
- | 'fleet_enrollment_tokens'
+ | 'enrollment_tokens'
| 'data_streams';
export type DynamicPage =
@@ -27,8 +26,9 @@ export type DynamicPage =
| 'add_integration_from_policy'
| 'add_integration_to_policy'
| 'edit_integration'
- | 'fleet_agent_list'
- | 'fleet_agent_details';
+ | 'agent_list'
+ | 'agent_details'
+ | 'agent_details_logs';
export type Page = StaticPage | DynamicPage;
@@ -42,20 +42,21 @@ export const INTEGRATIONS_BASE_PATH = '/app/integrations';
// If routing paths are changed here, please also check to see if
// `pagePathGetters()`, below, needs any modifications
export const FLEET_ROUTING_PATHS = {
- overview: '/',
+ fleet: '/:tabId',
+ agents: '/agents',
+ agent_details: '/agents/:agentId/:tabId?',
+ agent_details_logs: '/agents/:agentId/logs',
policies: '/policies',
policies_list: '/policies',
policy_details: '/policies/:policyId/:tabId?',
policy_details_settings: '/policies/:policyId/settings',
- add_integration_from_policy: '/policies/:policyId/add-integration',
- add_integration_to_policy: '/integrations/:pkgkey/add-integration/:integration?',
edit_integration: '/policies/:policyId/edit-integration/:packagePolicyId',
- fleet: '/fleet',
- fleet_agent_list: '/fleet/agents',
- fleet_agent_details: '/fleet/agents/:agentId/:tabId?',
- fleet_agent_details_logs: '/fleet/agents/:agentId/logs',
- fleet_enrollment_tokens: '/fleet/enrollment-tokens',
+ add_integration_from_policy: '/policies/:policyId/add-integration',
+ enrollment_tokens: '/enrollment-tokens',
data_streams: '/data-streams',
+
+ // TODO: Move this to the integrations app
+ add_integration_to_policy: '/integrations/:pkgkey/add-integration/:integration?',
};
export const INTEGRATIONS_ROUTING_PATHS = {
@@ -120,15 +121,12 @@ export const pagePathGetters: {
FLEET_BASE_PATH,
`/policies/${policyId}/edit-integration/${packagePolicyId}`,
],
- fleet: () => [FLEET_BASE_PATH, '/fleet'],
- fleet_agent_list: ({ kuery }) => [
- FLEET_BASE_PATH,
- `/fleet/agents${kuery ? `?kuery=${kuery}` : ''}`,
- ],
- fleet_agent_details: ({ agentId, tabId, logQuery }) => [
+ agent_list: ({ kuery }) => [FLEET_BASE_PATH, `/agents${kuery ? `?kuery=${kuery}` : ''}`],
+ agent_details: ({ agentId, tabId, logQuery }) => [
FLEET_BASE_PATH,
- `/fleet/agents/${agentId}${tabId ? `/${tabId}` : ''}${logQuery ? `?_q=${logQuery}` : ''}`,
+ `/agents/${agentId}${tabId ? `/${tabId}` : ''}${logQuery ? `?_q=${logQuery}` : ''}`,
],
- fleet_enrollment_tokens: () => [FLEET_BASE_PATH, '/fleet/enrollment-tokens'],
+ agent_details_logs: ({ agentId }) => [FLEET_BASE_PATH, `/agents/${agentId}/logs`],
+ enrollment_tokens: () => [FLEET_BASE_PATH, '/enrollment-tokens'],
data_streams: () => [FLEET_BASE_PATH, '/data-streams'],
};
diff --git a/x-pack/plugins/fleet/public/layouts/without_header.tsx b/x-pack/plugins/fleet/public/layouts/without_header.tsx
index 220ee592d7d07..d9481d44359c2 100644
--- a/x-pack/plugins/fleet/public/layouts/without_header.tsx
+++ b/x-pack/plugins/fleet/public/layouts/without_header.tsx
@@ -11,6 +11,13 @@ import { EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui';
export const Wrapper = styled.div`
background-color: ${(props) => props.theme.eui.euiColorEmptyShade};
+
+ // HACK: Kibana introduces a div element around the app component that results in us
+ // being unable to stretch this Wrapper to full height via flex: 1. This calc sets
+ // the min height to the viewport size minus the height of the two global Kibana headers.
+ min-height: calc(
+ 100vh - ${(props) => parseFloat(props.theme.eui.euiHeaderHeightCompensation) * 2}px
+ );
`;
export const Page = styled(EuiPage)`
diff --git a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts
index 16fa34e2d0b3d..5d1567936bcb0 100644
--- a/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts
+++ b/x-pack/plugins/fleet/public/mock/plugin_dependencies.ts
@@ -8,6 +8,7 @@
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
import { licensingMock } from '../../../licensing/public/mocks';
import { homePluginMock } from '../../../../../src/plugins/home/public/mocks';
+import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks';
import type { MockedFleetSetupDeps, MockedFleetStartDeps } from './types';
@@ -22,5 +23,6 @@ export const createSetupDepsMock = (): MockedFleetSetupDeps => {
export const createStartDepsMock = (): MockedFleetStartDeps => {
return {
data: dataPluginMock.createStartContract(),
+ navigation: navigationPluginMock.createStartContract(),
};
};
diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts
index f9515ca925a4a..7b71b21006864 100644
--- a/x-pack/plugins/fleet/public/plugin.ts
+++ b/x-pack/plugins/fleet/public/plugin.ts
@@ -14,6 +14,8 @@ import type {
} from 'src/core/public';
import { i18n } from '@kbn/i18n';
+import type { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
+
import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '../../../../src/core/public';
import type {
DataPublicPluginSetup,
@@ -64,6 +66,7 @@ export interface FleetSetupDeps {
export interface FleetStartDeps {
data: DataPublicPluginStart;
+ navigation: NavigationPublicPluginStart;
}
export interface FleetStartServices extends CoreStart, FleetStartDeps {
diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
index 23277976968a9..23eaaeac1439d 100644
--- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
+++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
@@ -132,7 +132,7 @@ const ActionResultsSummaryComponent: React.FC = ({
diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx
index affc600847284..6ff60d30d23bf 100644
--- a/x-pack/plugins/osquery/public/results/results_table.tsx
+++ b/x-pack/plugins/osquery/public/results/results_table.tsx
@@ -66,7 +66,7 @@ const ResultsTableComponent: React.FC = ({
const getFleetAppUrl = useCallback(
(agentId) =>
getUrlForApp('fleet', {
- path: `#` + pagePathGetters.fleet_agent_details({ agentId }),
+ path: `#` + pagePathGetters.agent_details({ agentId }),
}),
[getUrlForApp]
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx
index 31069b1939ce9..e03427671798d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx
@@ -138,13 +138,13 @@ export const useEndpointActionItems = (
navigateAppId: 'fleet',
navigateOptions: {
path: `#${
- pagePathGetters.fleet_agent_details({
+ pagePathGetters.agent_details({
agentId: fleetAgentId,
})[1]
}`,
},
href: `${getUrlForApp('fleet')}#${
- pagePathGetters.fleet_agent_details({
+ pagePathGetters.agent_details({
agentId: fleetAgentId,
})[1]
}`,
@@ -162,13 +162,13 @@ export const useEndpointActionItems = (
navigateAppId: 'fleet',
navigateOptions: {
path: `#${
- pagePathGetters.fleet_agent_details({
+ pagePathGetters.agent_details({
agentId: fleetAgentId,
})[1]
}/activity?openReassignFlyout=true`,
},
href: `${getUrlForApp('fleet')}#${
- pagePathGetters.fleet_agent_details({
+ pagePathGetters.agent_details({
agentId: fleetAgentId,
})[1]
}/activity?openReassignFlyout=true`,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index 86f1e32e751ee..1ac5c289c87cf 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -1108,13 +1108,13 @@ describe('when on the endpoint list page', () => {
});
it('navigates to the Ingest Agent Details page', async () => {
const agentDetailsLink = await renderResult.findByTestId('agentDetailsLink');
- expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet#/fleet/agents/${agentId}`);
+ expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet#/agents/${agentId}`);
});
it('navigates to the Ingest Agent Details page with policy reassign', async () => {
const agentPolicyReassignLink = await renderResult.findByTestId('agentPolicyReassignLink');
expect(agentPolicyReassignLink.getAttribute('href')).toEqual(
- `/app/fleet#/fleet/agents/${agentId}/activity?openReassignFlyout=true`
+ `/app/fleet#/agents/${agentId}/activity?openReassignFlyout=true`
);
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
index 410afb4684cd5..d1dab3dd07a7e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
@@ -515,12 +515,12 @@ export const EndpointList = () => {
agentsLink: (
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
index 8c1eb611cb5a1..c54c12981c771 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
@@ -156,9 +156,7 @@ export const isolationRequestHandler = function (
commentLines.push(`${isolate ? 'I' : 'Uni'}solate action was sent to the following Agents:`);
// lines of markdown links, inside a code block
- commentLines.push(
- `${agentIDs.map((a) => `- [${a}](/app/fleet#/fleet/agents/${a})`).join('\n')}`
- );
+ commentLines.push(`${agentIDs.map((a) => `- [${a}](/app/fleet#/agents/${a})`).join('\n')}`);
if (req.body.comment) {
commentLines.push(`\n\nWith Comment:\n> ${req.body.comment}`);
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index fb936a5838781..db382a677fbe8 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -8829,7 +8829,6 @@
"xpack.fleet.agentList.addButton": "エージェントの追加",
"xpack.fleet.agentList.agentUpgradeLabel": "アップグレードが利用可能です",
"xpack.fleet.agentList.clearFiltersLinkText": "フィルターを消去",
- "xpack.fleet.agentList.enrollButton": "エージェントの追加",
"xpack.fleet.agentList.errorFetchingDataTitle": "エージェントの取り込みエラー",
"xpack.fleet.agentList.forceUnenrollOneButton": "強制的に登録解除する",
"xpack.fleet.agentList.hostColumnTitle": "ホスト",
@@ -8903,8 +8902,6 @@
"xpack.fleet.agentPolicyList.noAgentPoliciesPrompt": "エージェントポリシーがありません",
"xpack.fleet.agentPolicyList.noFilteredAgentPoliciesPrompt": "エージェントポリシーが見つかりません。{clearFiltersLink}",
"xpack.fleet.agentPolicyList.packagePoliciesCountColumnTitle": "統合",
- "xpack.fleet.agentPolicyList.pageSubtitle": "エージェントポリシーを使用すると、エージェントとエージェントが収集するデータを管理できます。",
- "xpack.fleet.agentPolicyList.pageTitle": "エージェントポリシー",
"xpack.fleet.agentPolicyList.reloadAgentPoliciesButtonText": "再読み込み",
"xpack.fleet.agentPolicyList.updatedOnColumnTitle": "最終更新日",
"xpack.fleet.agentPolicySummaryLine.hostedPolicyTooltip": "このポリシーはFleet外で管理されます。このポリシーに関連するほとんどのアクションは使用できません。",
@@ -8914,8 +8911,6 @@
"xpack.fleet.agentReassignPolicy.flyoutTitle": "新しいエージェントポリシーを割り当てる",
"xpack.fleet.agentReassignPolicy.selectPolicyLabel": "エージェントポリシー",
"xpack.fleet.agentReassignPolicy.successSingleNotificationTitle": "エージェントポリシーが再割り当てされました",
- "xpack.fleet.agents.pageSubtitle": "ポリシーの更新を管理し、任意のサイズのエージェントのグループにデプロイします。",
- "xpack.fleet.agents.pageTitle": "エージェント",
"xpack.fleet.agentsInitializationErrorMessageTitle": "Elasticエージェントの集中管理を初期化できません",
"xpack.fleet.agentStatus.healthyLabel": "正常",
"xpack.fleet.agentStatus.inactiveLabel": "非アクティブ",
@@ -8924,13 +8919,10 @@
"xpack.fleet.agentStatus.updatingLabel": "更新中",
"xpack.fleet.appNavigation.agentsLinkText": "エージェント",
"xpack.fleet.appNavigation.dataStreamsLinkText": "データストリーム",
- "xpack.fleet.appNavigation.overviewLinkText": "概要",
"xpack.fleet.appNavigation.policiesLinkText": "ポリシー",
"xpack.fleet.appNavigation.sendFeedbackButton": "フィードバックを送信",
"xpack.fleet.appNavigation.settingsButton": "Fleet 設定",
"xpack.fleet.appTitle": "Fleet",
- "xpack.fleet.betaBadge.labelText": "ベータ",
- "xpack.fleet.betaBadge.tooltipText": "このプラグインは本番環境用ではありません。バグについてはディスカッションフォーラムで報告してください。",
"xpack.fleet.breadcrumbs.addPackagePolicyPageTitle": "統合の追加",
"xpack.fleet.breadcrumbs.agentsPageTitle": "エージェント",
"xpack.fleet.breadcrumbs.allIntegrationsPageTitle": "すべて",
@@ -8939,7 +8931,6 @@
"xpack.fleet.breadcrumbs.editPackagePolicyPageTitle": "統合の編集",
"xpack.fleet.breadcrumbs.enrollmentTokensPageTitle": "登録トークン",
"xpack.fleet.breadcrumbs.installedIntegrationsPageTitle": "インストール済み",
- "xpack.fleet.breadcrumbs.overviewPageTitle": "概要",
"xpack.fleet.breadcrumbs.policiesPageTitle": "ポリシー",
"xpack.fleet.config.invalidPackageVersionError": "有効なサーバーまたはキーワード「latest」でなければなりません",
"xpack.fleet.copyAgentPolicy.confirmModal.cancelButtonLabel": "キャンセル",
@@ -8998,8 +8989,6 @@
"xpack.fleet.dataStreamList.namespaceColumnTitle": "名前空間",
"xpack.fleet.dataStreamList.noDataStreamsPrompt": "データストリームがありません",
"xpack.fleet.dataStreamList.noFilteredDataStreamsMessage": "一致するデータストリームが見つかりません",
- "xpack.fleet.dataStreamList.pageSubtitle": "エージェントが作成したデータを管理します。",
- "xpack.fleet.dataStreamList.pageTitle": "データストリーム",
"xpack.fleet.dataStreamList.reloadDataStreamsButtonText": "再読み込み",
"xpack.fleet.dataStreamList.searchPlaceholderTitle": "データストリームをフィルター",
"xpack.fleet.dataStreamList.sizeColumnTitle": "サイズ",
@@ -9188,8 +9177,6 @@
"xpack.fleet.integrations.updatePackage.updatePackageButtonLabel": "最新バージョンに更新",
"xpack.fleet.invalidLicenseDescription": "現在のライセンスは期限切れです。登録されたビートエージェントは引き続き動作しますが、Elastic Fleet インターフェイスにアクセスするには有効なライセンスが必要です。",
"xpack.fleet.invalidLicenseTitle": "ライセンスの期限切れ",
- "xpack.fleet.listTabs.agentTitle": "エージェント",
- "xpack.fleet.listTabs.enrollmentTokensTitle": "登録トークン",
"xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "名前空間に無効な文字が含まれています",
"xpack.fleet.namespaceValidation.lowercaseErrorMessage": "名前空間は小文字で指定する必要があります",
"xpack.fleet.namespaceValidation.requiredErrorMessage": "名前空間は必須です",
@@ -9202,33 +9189,8 @@
"xpack.fleet.noAccess.accessDeniedDescription": "Elastic Fleet にアクセスする権限がありません。Elastic Fleet を使用するには、このアプリケーションの読み取り権または全権を含むユーザーロールが必要です。",
"xpack.fleet.noAccess.accessDeniedTitle": "アクセスが拒否されました",
"xpack.fleet.oldAppTitle": "Ingest Manager",
- "xpack.fleet.overviewAgentActiveTitle": "アクティブ",
- "xpack.fleet.overviewAgentErrorTitle": "エラー",
- "xpack.fleet.overviewAgentOfflineTitle": "オフライン",
- "xpack.fleet.overviewAgentTotalTitle": "合計エージェント数",
- "xpack.fleet.overviewDatastreamNamespacesTitle": "名前空間",
- "xpack.fleet.overviewDatastreamSizeTitle": "合計サイズ",
- "xpack.fleet.overviewDatastreamTotalTitle": "データストリーム",
- "xpack.fleet.overviewIntegrationsInstalledTitle": "インストール済み",
- "xpack.fleet.overviewIntegrationsTotalTitle": "合計利用可能数",
- "xpack.fleet.overviewIntegrationsUpdatesAvailableTitle": "更新が可能です",
- "xpack.fleet.overviewPackagePolicyTitle": "使用済みの統合",
- "xpack.fleet.overviewPageAgentsPanelTitle": "エージェント",
- "xpack.fleet.overviewPageDataStreamsPanelAction": "データストリームを表示",
- "xpack.fleet.overviewPageDataStreamsPanelTitle": "データストリーム",
- "xpack.fleet.overviewPageDataStreamsPanelTooltip": "エージェントが収集するデータはさまざまなデータストリームに整理されます。",
- "xpack.fleet.overviewPageEnrollAgentButton": "エージェントの追加",
- "xpack.fleet.overviewPageFleetPanelAction": "エージェントを表示",
- "xpack.fleet.overviewPageFleetPanelTooltip": "Fleetを使用して、中央の場所からエージェントを登録し、ポリシーを管理します。",
- "xpack.fleet.overviewPageIntegrationsPanelAction": "統合を表示",
- "xpack.fleet.overviewPageIntegrationsPanelTitle": "統合",
- "xpack.fleet.overviewPageIntegrationsPanelTooltip": "Elastic Stackの統合を参照し、インストールします。統合をエージェントポリシーに追加し、データの送信を開始します。",
- "xpack.fleet.overviewPagePoliciesPanelAction": "ポリシーを表示",
- "xpack.fleet.overviewPagePoliciesPanelTitle": "エージェントポリシー",
- "xpack.fleet.overviewPagePoliciesPanelTooltip": "エージェントポリシーを使用すると、エージェントが収集するデータを管理できます。",
"xpack.fleet.overviewPageSubtitle": "Elasticエージェントとポリシーを中央の場所で管理します。",
"xpack.fleet.overviewPageTitle": "Fleet",
- "xpack.fleet.overviewPolicyTotalTitle": "合計利用可能数",
"xpack.fleet.packagePolicyInputOverrideError": "パッケージ{packageName}には入力タイプ{inputType}が存在しません。",
"xpack.fleet.packagePolicyStreamOverrideError": "パッケージ{packageName}の{inputType}にはデータストリーム{streamSet}が存在しません",
"xpack.fleet.packagePolicyStreamVarOverrideError": "パッケージ{packageName}の{inputType}の{streamSet}にはVar {varName}が存在しません",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 998b2a4c67287..2f86597f4c5c5 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -8906,7 +8906,6 @@
"xpack.fleet.agentList.addButton": "添加代理",
"xpack.fleet.agentList.agentUpgradeLabel": "升级可用",
"xpack.fleet.agentList.clearFiltersLinkText": "清除筛选",
- "xpack.fleet.agentList.enrollButton": "添加代理",
"xpack.fleet.agentList.errorFetchingDataTitle": "获取代理时出错",
"xpack.fleet.agentList.forceUnenrollOneButton": "强制取消注册",
"xpack.fleet.agentList.hostColumnTitle": "主机",
@@ -8981,8 +8980,6 @@
"xpack.fleet.agentPolicyList.noAgentPoliciesPrompt": "无代理策略",
"xpack.fleet.agentPolicyList.noFilteredAgentPoliciesPrompt": "找不到任何代理策略。{clearFiltersLink}",
"xpack.fleet.agentPolicyList.packagePoliciesCountColumnTitle": "集成",
- "xpack.fleet.agentPolicyList.pageSubtitle": "使用代理策略管理代理及其收集的数据。",
- "xpack.fleet.agentPolicyList.pageTitle": "代理策略",
"xpack.fleet.agentPolicyList.reloadAgentPoliciesButtonText": "重新加载",
"xpack.fleet.agentPolicyList.updatedOnColumnTitle": "上次更新时间",
"xpack.fleet.agentPolicySummaryLine.hostedPolicyTooltip": "此策略是在 Fleet 外进行管理的。与此策略相关的操作多数不可用。",
@@ -8994,8 +8991,6 @@
"xpack.fleet.agentReassignPolicy.policyDescription": "选定代理策略将收集 {count, plural, other {{countValue} 个集成} }的数据:",
"xpack.fleet.agentReassignPolicy.selectPolicyLabel": "代理策略",
"xpack.fleet.agentReassignPolicy.successSingleNotificationTitle": "代理策略已重新分配",
- "xpack.fleet.agents.pageSubtitle": "管理策略更新并将其部署到一组任意大小的代理。",
- "xpack.fleet.agents.pageTitle": "代理",
"xpack.fleet.agentsInitializationErrorMessageTitle": "无法为 Elastic 代理初始化集中管理",
"xpack.fleet.agentStatus.healthyLabel": "运行正常",
"xpack.fleet.agentStatus.inactiveLabel": "非活动",
@@ -9004,13 +8999,10 @@
"xpack.fleet.agentStatus.updatingLabel": "正在更新",
"xpack.fleet.appNavigation.agentsLinkText": "代理",
"xpack.fleet.appNavigation.dataStreamsLinkText": "数据流",
- "xpack.fleet.appNavigation.overviewLinkText": "概览",
"xpack.fleet.appNavigation.policiesLinkText": "策略",
"xpack.fleet.appNavigation.sendFeedbackButton": "发送反馈",
"xpack.fleet.appNavigation.settingsButton": "Fleet 设置",
"xpack.fleet.appTitle": "Fleet",
- "xpack.fleet.betaBadge.labelText": "公测版",
- "xpack.fleet.betaBadge.tooltipText": "不推荐在生产环境中使用此插件。请在我们讨论论坛中报告错误。",
"xpack.fleet.breadcrumbs.addPackagePolicyPageTitle": "添加集成",
"xpack.fleet.breadcrumbs.agentsPageTitle": "代理",
"xpack.fleet.breadcrumbs.allIntegrationsPageTitle": "全部",
@@ -9019,7 +9011,6 @@
"xpack.fleet.breadcrumbs.editPackagePolicyPageTitle": "编辑集成",
"xpack.fleet.breadcrumbs.enrollmentTokensPageTitle": "注册令牌",
"xpack.fleet.breadcrumbs.installedIntegrationsPageTitle": "已安装",
- "xpack.fleet.breadcrumbs.overviewPageTitle": "概览",
"xpack.fleet.breadcrumbs.policiesPageTitle": "策略",
"xpack.fleet.config.invalidPackageVersionError": "必须是有效的 semver 或关键字 `latest`",
"xpack.fleet.copyAgentPolicy.confirmModal.cancelButtonLabel": "取消",
@@ -9080,8 +9071,6 @@
"xpack.fleet.dataStreamList.namespaceColumnTitle": "命名空间",
"xpack.fleet.dataStreamList.noDataStreamsPrompt": "无数据流",
"xpack.fleet.dataStreamList.noFilteredDataStreamsMessage": "找不到匹配的数据流",
- "xpack.fleet.dataStreamList.pageSubtitle": "管理您的代理创建的数据。",
- "xpack.fleet.dataStreamList.pageTitle": "数据流",
"xpack.fleet.dataStreamList.reloadDataStreamsButtonText": "重新加载",
"xpack.fleet.dataStreamList.searchPlaceholderTitle": "筛选数据流",
"xpack.fleet.dataStreamList.sizeColumnTitle": "大小",
@@ -9274,8 +9263,6 @@
"xpack.fleet.integrations.updatePackage.updatePackageButtonLabel": "更新到最新版本",
"xpack.fleet.invalidLicenseDescription": "您当前的许可证已过期。已注册 Beats 代理将继续工作,但您需要有效的许可证,才能访问 Elastic Fleet 界面。",
"xpack.fleet.invalidLicenseTitle": "已过期许可证",
- "xpack.fleet.listTabs.agentTitle": "代理",
- "xpack.fleet.listTabs.enrollmentTokensTitle": "注册令牌",
"xpack.fleet.namespaceValidation.invalidCharactersErrorMessage": "命名空间包含无效字符",
"xpack.fleet.namespaceValidation.lowercaseErrorMessage": "命名空间必须小写",
"xpack.fleet.namespaceValidation.requiredErrorMessage": "“命名空间”必填",
@@ -9288,33 +9275,8 @@
"xpack.fleet.noAccess.accessDeniedDescription": "您无权访问 Elastic Fleet。要使用 Elastic Fleet,您需要包含此应用程序读取权限或所有权限的用户角色。",
"xpack.fleet.noAccess.accessDeniedTitle": "访问被拒绝",
"xpack.fleet.oldAppTitle": "采集管理器",
- "xpack.fleet.overviewAgentActiveTitle": "活动",
- "xpack.fleet.overviewAgentErrorTitle": "错误",
- "xpack.fleet.overviewAgentOfflineTitle": "脱机",
- "xpack.fleet.overviewAgentTotalTitle": "代理总数",
- "xpack.fleet.overviewDatastreamNamespacesTitle": "命名空间",
- "xpack.fleet.overviewDatastreamSizeTitle": "总大小",
- "xpack.fleet.overviewDatastreamTotalTitle": "数据流",
- "xpack.fleet.overviewIntegrationsInstalledTitle": "已安装",
- "xpack.fleet.overviewIntegrationsTotalTitle": "可用总计",
- "xpack.fleet.overviewIntegrationsUpdatesAvailableTitle": "可用更新",
- "xpack.fleet.overviewPackagePolicyTitle": "已使用的集成",
- "xpack.fleet.overviewPageAgentsPanelTitle": "代理",
- "xpack.fleet.overviewPageDataStreamsPanelAction": "查看数据流",
- "xpack.fleet.overviewPageDataStreamsPanelTitle": "数据流",
- "xpack.fleet.overviewPageDataStreamsPanelTooltip": "您的代理收集的数据组织到各种数据流中。",
- "xpack.fleet.overviewPageEnrollAgentButton": "添加代理",
- "xpack.fleet.overviewPageFleetPanelAction": "查看代理",
- "xpack.fleet.overviewPageFleetPanelTooltip": "使用 Fleet 注册代理并从中央位置管理其策略。",
- "xpack.fleet.overviewPageIntegrationsPanelAction": "查看集成",
- "xpack.fleet.overviewPageIntegrationsPanelTitle": "集成",
- "xpack.fleet.overviewPageIntegrationsPanelTooltip": "浏览并安装适用于 Elastic Stack 的集成。将集成添加到您的代理策略,以开始发送数据。",
- "xpack.fleet.overviewPagePoliciesPanelAction": "查看策略",
- "xpack.fleet.overviewPagePoliciesPanelTitle": "代理策略",
- "xpack.fleet.overviewPagePoliciesPanelTooltip": "使用代理策略控制您的代理收集的数据。",
"xpack.fleet.overviewPageSubtitle": "在集中位置管理 Elastic 代理及其策略。",
"xpack.fleet.overviewPageTitle": "Fleet",
- "xpack.fleet.overviewPolicyTotalTitle": "可用总计",
"xpack.fleet.packagePolicyInputOverrideError": "输入类型 {inputType} 在软件包 {packageName} 上不存在",
"xpack.fleet.packagePolicyStreamOverrideError": "数据流 {streamSet} 在软件包 {packageName} 的 {inputType} 上不存在",
"xpack.fleet.packagePolicyStreamVarOverrideError": "变量 {varName} 在软件包 {packageName} 的 {inputType} 的 {streamSet} 上不存在",
diff --git a/x-pack/test/fleet_functional/apps/fleet/agents_page.ts b/x-pack/test/fleet_functional/apps/fleet/agents_page.ts
new file mode 100644
index 0000000000000..515eaa65f5310
--- /dev/null
+++ b/x-pack/test/fleet_functional/apps/fleet/agents_page.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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 { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getPageObjects, getService }: FtrProviderContext) {
+ const { agentsPage } = getPageObjects(['agentsPage']);
+
+ describe('When in the Fleet application', function () {
+ this.tags(['ciGroup7']);
+
+ describe('and on the agents page', () => {
+ before(async () => {
+ await agentsPage.navigateToAgentsPage();
+ });
+
+ it('should show the agents tab', async () => {
+ await agentsPage.agentsTabExistsOrFail();
+ });
+
+ it('should show the agent policies tab', async () => {
+ await agentsPage.agentPoliciesTabExistsOrFail();
+ });
+
+ it('should show the enrollment tokens tab', async () => {
+ await agentsPage.enrollmentTokensTabExistsOrFail();
+ });
+
+ it('should show the data streams tab', async () => {
+ await agentsPage.dataStreamsTabExistsOrFail();
+ });
+ });
+ });
+}
diff --git a/x-pack/test/fleet_functional/apps/fleet/index.ts b/x-pack/test/fleet_functional/apps/fleet/index.ts
index 23a070cb79934..ec16e2d857183 100644
--- a/x-pack/test/fleet_functional/apps/fleet/index.ts
+++ b/x-pack/test/fleet_functional/apps/fleet/index.ts
@@ -12,6 +12,6 @@ export default function (providerContext: FtrProviderContext) {
describe('endpoint', function () {
this.tags('ciGroup7');
- loadTestFile(require.resolve('./overview_page'));
+ loadTestFile(require.resolve('./agents_page'));
});
}
diff --git a/x-pack/test/fleet_functional/apps/fleet/overview_page.ts b/x-pack/test/fleet_functional/apps/fleet/overview_page.ts
deleted file mode 100644
index 3d3b069665448..0000000000000
--- a/x-pack/test/fleet_functional/apps/fleet/overview_page.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { FtrProviderContext } from '../../ftr_provider_context';
-
-export default function ({ getPageObjects, getService }: FtrProviderContext) {
- const { overviewPage } = getPageObjects(['overviewPage']);
-
- describe('When in the Fleet application', function () {
- this.tags(['ciGroup7']);
-
- describe('and on the Overview page', () => {
- before(async () => {
- await overviewPage.navigateToOverview();
- });
-
- it('should show the Integrations section', async () => {
- await overviewPage.integrationsSectionExistsOrFail();
- });
-
- it('should show the Agents section', async () => {
- await overviewPage.agentSectionExistsOrFail();
- });
-
- it('should show the Agent policies section', async () => {
- await overviewPage.agentPolicySectionExistsOrFail();
- });
-
- it('should show the Data streams section', async () => {
- await overviewPage.datastreamSectionExistsOrFail();
- });
- });
- });
-}
diff --git a/x-pack/test/fleet_functional/page_objects/overview_page.ts b/x-pack/test/fleet_functional/page_objects/agents_page.ts
similarity index 55%
rename from x-pack/test/fleet_functional/page_objects/overview_page.ts
rename to x-pack/test/fleet_functional/page_objects/agents_page.ts
index 2fd351184c7fe..99e9ebfdcc15a 100644
--- a/x-pack/test/fleet_functional/page_objects/overview_page.ts
+++ b/x-pack/test/fleet_functional/page_objects/agents_page.ts
@@ -11,31 +11,32 @@ import { PLUGIN_ID } from '../../../plugins/fleet/common';
// NOTE: import path below should be the deep path to the actual module - else we get CI errors
import { pagePathGetters } from '../../../plugins/fleet/public/constants/page_paths';
-export function OverviewPage({ getService, getPageObjects }: FtrProviderContext) {
+export function AgentsPage({ getService, getPageObjects }: FtrProviderContext) {
const pageObjects = getPageObjects(['common']);
const testSubjects = getService('testSubjects');
return {
- async navigateToOverview() {
+ async navigateToAgentsPage() {
await pageObjects.common.navigateToApp(PLUGIN_ID, {
- hash: pagePathGetters.overview()[1],
+ // Fleet's "/" route should redirect to "/agents"
+ hash: pagePathGetters.base()[1],
});
},
- async integrationsSectionExistsOrFail() {
- await testSubjects.existOrFail('fleet-integrations-section');
+ async agentsTabExistsOrFail() {
+ await testSubjects.existOrFail('fleet-agents-tab');
},
- async agentPolicySectionExistsOrFail() {
- await testSubjects.existOrFail('fleet-agent-policy-section');
+ async agentPoliciesTabExistsOrFail() {
+ await testSubjects.existOrFail('fleet-agent-policies-tab');
},
- async agentSectionExistsOrFail() {
- await testSubjects.existOrFail('fleet-agent-section');
+ async enrollmentTokensTabExistsOrFail() {
+ await testSubjects.existOrFail('fleet-enrollment-tokens-tab');
},
- async datastreamSectionExistsOrFail() {
- await testSubjects.existOrFail('fleet-datastream-section');
+ async dataStreamsTabExistsOrFail() {
+ await testSubjects.existOrFail('fleet-datastreams-tab');
},
};
}
diff --git a/x-pack/test/fleet_functional/page_objects/index.ts b/x-pack/test/fleet_functional/page_objects/index.ts
index 2c534285146e5..f0543aa3c7e89 100644
--- a/x-pack/test/fleet_functional/page_objects/index.ts
+++ b/x-pack/test/fleet_functional/page_objects/index.ts
@@ -6,9 +6,9 @@
*/
import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects';
-import { OverviewPage } from './overview_page';
+import { AgentsPage } from './agents_page';
export const pageObjects = {
...xpackFunctionalPageObjects,
- overviewPage: OverviewPage,
+ agentsPage: AgentsPage,
};
From c26d178937d9283691c9566a0c15740cee4bccbf Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Wed, 16 Jun 2021 10:32:48 -0700
Subject: [PATCH 39/98] [Reporting] remove unused reference to path.data config
(#102267)
---
x-pack/plugins/reporting/server/config/config.ts | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/x-pack/plugins/reporting/server/config/config.ts b/x-pack/plugins/reporting/server/config/config.ts
index 69eafba994b74..cd4dbd7c19956 100644
--- a/x-pack/plugins/reporting/server/config/config.ts
+++ b/x-pack/plugins/reporting/server/config/config.ts
@@ -6,8 +6,7 @@
*/
import { get } from 'lodash';
-import { Observable } from 'rxjs';
-import { first, map } from 'rxjs/operators';
+import { first } from 'rxjs/operators';
import { CoreSetup, PluginInitializerContext } from 'src/core/server';
import { LevelLogger } from '../lib';
import { createConfig$ } from './create_config';
@@ -43,7 +42,6 @@ interface Config {
}
interface KbnServerConfigType {
- path: { data: Observable };
server: {
basePath: string;
host: string;
@@ -68,9 +66,6 @@ export const buildConfig = async (
const serverInfo = http.getServerInfo();
const kbnConfig = {
- path: {
- data: initContext.config.legacy.globalConfig$.pipe(map((c) => c.path.data)),
- },
server: {
basePath: core.http.basePath.serverBasePath,
host: serverInfo.hostname,
From 154150732d3a0c6dd74f50a69e861baeeb551c5b Mon Sep 17 00:00:00 2001
From: Robert Oskamp
Date: Wed, 16 Jun 2021 19:42:44 +0200
Subject: [PATCH 40/98] [ML] Functional tests - fix and re-activate alerting
flyout test (#102368)
This PR fixes the ML alerting flyout tests and re-activates it.
---
x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts
index 777e6fd598f45..ba7243efe1773 100644
--- a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts
@@ -67,8 +67,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
let testJobId = '';
- // Failing: See https://github.com/elastic/kibana/issues/102012
- describe.skip('anomaly detection alert', function () {
+ describe('anomaly detection alert', function () {
this.tags('ciGroup13');
before(async () => {
@@ -119,11 +118,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await ml.testExecution.logTestStep('should preview the alert condition');
await ml.alerting.assertPreviewButtonState(false);
- await ml.alerting.setTestInterval('2y');
+ await ml.alerting.setTestInterval('5y');
await ml.alerting.assertPreviewButtonState(true);
// don't check the exact number provided by the backend, just make sure it's > 0
- await ml.alerting.checkPreview(/Found [1-9]\d* anomalies in the last 2y/);
+ await ml.alerting.checkPreview(/Found [1-9]\d* anomal(y|ies) in the last 5y/);
await ml.testExecution.logTestStep('should create an alert');
await pageObjects.triggersActionsUI.setAlertName('ml-test-alert');
From 8eea4914126fc079cfb77dce29e4ec1899c64807 Mon Sep 17 00:00:00 2001
From: Dario Gieselaar
Date: Wed, 16 Jun 2021 20:15:59 +0200
Subject: [PATCH 41/98] [RAC] Update alert documents in lifecycle rule type
helper (#101598)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../server/lib/services/get_service_alerts.ts | 6 +-
.../server/lib/rules/get_top_alerts.ts | 12 +-
x-pack/plugins/rule_registry/README.md | 3 -
.../create_rule_data_client_mock.ts | 42 ++
.../server/rule_data_client/index.ts | 37 +-
.../utils/create_lifecycle_rule_type.test.ts | 381 ++++++++++++++++++
.../create_lifecycle_rule_type_factory.ts | 29 +-
.../tests/alerts/rule_registry.ts | 23 +-
8 files changed, 501 insertions(+), 32 deletions(-)
create mode 100644 x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts
create mode 100644 x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts b/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts
index f58452ce4d916..2141570f521c0 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_alerts.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ALERT_UUID } from '@kbn/rule-data-utils/target/technical_field_names';
+import { EVENT_KIND } from '@kbn/rule-data-utils/target/technical_field_names';
import { RuleDataClient } from '../../../../rule_registry/server';
import {
SERVICE_NAME,
@@ -36,6 +36,7 @@ export async function getServiceAlerts({
...rangeQuery(start, end),
...environmentQuery(environment),
{ term: { [SERVICE_NAME]: serviceName } },
+ { term: { [EVENT_KIND]: 'signal' } },
],
should: [
{
@@ -64,9 +65,6 @@ export async function getServiceAlerts({
},
size: 100,
fields: ['*'],
- collapse: {
- field: ALERT_UUID,
- },
sort: {
'@timestamp': 'desc',
},
diff --git a/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts b/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts
index 9560de6ec00ff..db8191136686a 100644
--- a/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts
+++ b/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { ALERT_UUID, TIMESTAMP } from '@kbn/rule-data-utils/target/technical_field_names';
+import { EVENT_KIND, TIMESTAMP } from '@kbn/rule-data-utils/target/technical_field_names';
import { RuleDataClient } from '../../../../rule_registry/server';
import type { AlertStatus } from '../../../common/typings';
import { kqlQuery, rangeQuery, alertStatusQuery } from '../../utils/queries';
@@ -28,13 +28,15 @@ export async function getTopAlerts({
body: {
query: {
bool: {
- filter: [...rangeQuery(start, end), ...kqlQuery(kuery), ...alertStatusQuery(status)],
+ filter: [
+ ...rangeQuery(start, end),
+ ...kqlQuery(kuery),
+ ...alertStatusQuery(status),
+ { term: { [EVENT_KIND]: 'signal' } },
+ ],
},
},
fields: ['*'],
- collapse: {
- field: ALERT_UUID,
- },
size,
sort: {
[TIMESTAMP]: 'desc',
diff --git a/x-pack/plugins/rule_registry/README.md b/x-pack/plugins/rule_registry/README.md
index e12c2b29ed373..3fe6305a0d9f6 100644
--- a/x-pack/plugins/rule_registry/README.md
+++ b/x-pack/plugins/rule_registry/README.md
@@ -111,9 +111,6 @@ const response = await ruleDataClient.getReader().search({
},
size: 100,
fields: ['*'],
- collapse: {
- field: ALERT_UUID,
- },
sort: {
'@timestamp': 'desc',
},
diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts b/x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts
new file mode 100644
index 0000000000000..18f3c21fafc15
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/rule_data_client/create_rule_data_client_mock.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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 { Assign } from '@kbn/utility-types';
+import type { RuleDataClient } from '.';
+import { RuleDataReader, RuleDataWriter } from './types';
+
+type MockInstances> = {
+ [K in keyof T]: T[K] extends (...args: infer TArgs) => infer TReturn
+ ? jest.MockInstance
+ : never;
+};
+
+export function createRuleDataClientMock() {
+ const bulk = jest.fn();
+ const search = jest.fn();
+ const getDynamicIndexPattern = jest.fn();
+
+ return ({
+ createOrUpdateWriteTarget: jest.fn(({ namespace }) => Promise.resolve()),
+ getReader: jest.fn(() => ({
+ getDynamicIndexPattern,
+ search,
+ })),
+ getWriter: jest.fn(() => ({
+ bulk,
+ })),
+ } as unknown) as Assign<
+ RuleDataClient & Omit, 'options' | 'getClusterClient'>,
+ {
+ getWriter: (
+ ...args: Parameters
+ ) => MockInstances;
+ getReader: (
+ ...args: Parameters
+ ) => MockInstances;
+ }
+ >;
+}
diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/index.ts b/x-pack/plugins/rule_registry/server/rule_data_client/index.ts
index cd7467c903e52..cb336580ca354 100644
--- a/x-pack/plugins/rule_registry/server/rule_data_client/index.ts
+++ b/x-pack/plugins/rule_registry/server/rule_data_client/index.ts
@@ -4,6 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+
+import { isEmpty } from 'lodash';
import type { estypes } from '@elastic/elasticsearch';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import { IndexPatternsFetcher } from '../../../../../src/plugins/data/server';
@@ -44,15 +46,26 @@ export class RuleDataClient implements IRuleDataClient {
const clusterClient = await this.getClusterClient();
const indexPatternsFetcher = new IndexPatternsFetcher(clusterClient);
- const fields = await indexPatternsFetcher.getFieldsForWildcard({
- pattern: index,
- });
-
- return {
- fields,
- timeFieldName: '@timestamp',
- title: index,
- };
+ try {
+ const fields = await indexPatternsFetcher.getFieldsForWildcard({
+ pattern: index,
+ });
+
+ return {
+ fields,
+ timeFieldName: '@timestamp',
+ title: index,
+ };
+ } catch (err) {
+ if (err.output?.payload?.code === 'no_matching_indices') {
+ return {
+ fields: [],
+ timeFieldName: '@timestamp',
+ title: index,
+ };
+ }
+ throw err;
+ }
},
};
}
@@ -127,6 +140,12 @@ export class RuleDataClient implements IRuleDataClient {
const mappings: estypes.MappingTypeMapping = simulateResponse.template.mappings;
+ if (isEmpty(mappings)) {
+ throw new Error(
+ 'No mappings would be generated for this index, possibly due to failed/misconfigured bootstrapping'
+ );
+ }
+
await clusterClient.indices.putMapping({ index: `${alias}*`, body: mappings });
}
}
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
new file mode 100644
index 0000000000000..85e69eb51fd02
--- /dev/null
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
@@ -0,0 +1,381 @@
+/*
+ * Copyright 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 { schema } from '@kbn/config-schema';
+import { loggerMock } from '@kbn/logging/target/mocks';
+import { castArray, omit, mapValues } from 'lodash';
+import { RuleDataClient } from '../rule_data_client';
+import { createRuleDataClientMock } from '../rule_data_client/create_rule_data_client_mock';
+import { createLifecycleRuleTypeFactory } from './create_lifecycle_rule_type_factory';
+
+type RuleTestHelpers = ReturnType;
+
+function createRule() {
+ const ruleDataClientMock = createRuleDataClientMock();
+
+ const factory = createLifecycleRuleTypeFactory({
+ ruleDataClient: (ruleDataClientMock as unknown) as RuleDataClient,
+ logger: loggerMock.create(),
+ });
+
+ let nextAlerts: Array<{ id: string; fields: Record }> = [];
+
+ const type = factory({
+ actionGroups: [
+ {
+ id: 'warning',
+ name: 'warning',
+ },
+ ],
+ defaultActionGroupId: 'warning',
+ executor: async ({ services }) => {
+ nextAlerts.forEach((alert) => {
+ services.alertWithLifecycle(alert);
+ });
+ nextAlerts = [];
+ },
+ id: 'test_type',
+ minimumLicenseRequired: 'basic',
+ name: 'Test type',
+ producer: 'test',
+ actionVariables: {
+ context: [],
+ params: [],
+ state: [],
+ },
+ validate: {
+ params: schema.object({}, { unknowns: 'allow' }),
+ },
+ });
+
+ let state: Record = {};
+ let previousStartedAt: Date | null;
+ const createdAt = new Date('2021-06-16T09:00:00.000Z');
+
+ const scheduleActions = jest.fn();
+
+ const alertInstanceFactory = () => {
+ return {
+ scheduleActions,
+ } as any;
+ };
+
+ return {
+ alertWithLifecycle: async (alerts: Array<{ id: string; fields: Record }>) => {
+ nextAlerts = alerts;
+
+ const startedAt = new Date((previousStartedAt ?? createdAt).getTime() + 60000);
+
+ scheduleActions.mockClear();
+
+ state = await type.executor({
+ alertId: 'alertId',
+ createdBy: 'createdBy',
+ name: 'name',
+ params: {},
+ previousStartedAt,
+ startedAt,
+ rule: {
+ actions: [],
+ consumer: 'consumer',
+ createdAt,
+ createdBy: 'createdBy',
+ enabled: true,
+ name: 'name',
+ notifyWhen: 'onActionGroupChange',
+ producer: 'producer',
+ ruleTypeId: 'ruleTypeId',
+ ruleTypeName: 'ruleTypeName',
+ schedule: {
+ interval: '1m',
+ },
+ tags: ['tags'],
+ throttle: null,
+ updatedAt: createdAt,
+ updatedBy: 'updatedBy',
+ },
+ services: {
+ alertInstanceFactory,
+ savedObjectsClient: {} as any,
+ scopedClusterClient: {} as any,
+ },
+ spaceId: 'spaceId',
+ state,
+ tags: ['tags'],
+ updatedBy: 'updatedBy',
+ namespace: 'namespace',
+ });
+
+ previousStartedAt = startedAt;
+ },
+ scheduleActions,
+ ruleDataClientMock,
+ };
+}
+
+describe('createLifecycleRuleTypeFactory', () => {
+ describe('with a new rule', () => {
+ let helpers: RuleTestHelpers;
+
+ beforeEach(() => {
+ helpers = createRule();
+ });
+
+ describe('when alerts are new', () => {
+ beforeEach(async () => {
+ await helpers.alertWithLifecycle([
+ {
+ id: 'opbeans-java',
+ fields: {
+ 'service.name': 'opbeans-java',
+ },
+ },
+ {
+ id: 'opbeans-node',
+ fields: {
+ 'service.name': 'opbeans-node',
+ },
+ },
+ ]);
+ });
+
+ it('writes the correct alerts', () => {
+ expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(1);
+
+ const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[0][0].body!;
+
+ const documents = body.filter((op: any) => !('index' in op)) as any[];
+
+ const evaluationDocuments = documents.filter((doc) => doc['event.kind'] === 'event');
+ const alertDocuments = documents.filter((doc) => doc['event.kind'] === 'signal');
+
+ expect(evaluationDocuments.length).toBe(2);
+ expect(alertDocuments.length).toBe(2);
+
+ expect(
+ alertDocuments.every((doc) => doc['kibana.rac.alert.status'] === 'open')
+ ).toBeTruthy();
+
+ expect(
+ alertDocuments.every((doc) => doc['kibana.rac.alert.duration.us'] === 0)
+ ).toBeTruthy();
+
+ expect(alertDocuments.every((doc) => doc['event.action'] === 'open')).toBeTruthy();
+
+ expect(documents.map((doc) => omit(doc, 'kibana.rac.alert.uuid'))).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "@timestamp": "2021-06-16T09:01:00.000Z",
+ "event.action": "open",
+ "event.kind": "event",
+ "kibana.rac.alert.duration.us": 0,
+ "kibana.rac.alert.id": "opbeans-java",
+ "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
+ "kibana.rac.alert.status": "open",
+ "rule.category": "Test type",
+ "rule.id": "test_type",
+ "rule.name": "name",
+ "rule.uuid": "alertId",
+ "service.name": "opbeans-java",
+ "tags": Array [
+ "tags",
+ ],
+ },
+ Object {
+ "@timestamp": "2021-06-16T09:01:00.000Z",
+ "event.action": "open",
+ "event.kind": "event",
+ "kibana.rac.alert.duration.us": 0,
+ "kibana.rac.alert.id": "opbeans-node",
+ "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
+ "kibana.rac.alert.status": "open",
+ "rule.category": "Test type",
+ "rule.id": "test_type",
+ "rule.name": "name",
+ "rule.uuid": "alertId",
+ "service.name": "opbeans-node",
+ "tags": Array [
+ "tags",
+ ],
+ },
+ Object {
+ "@timestamp": "2021-06-16T09:01:00.000Z",
+ "event.action": "open",
+ "event.kind": "signal",
+ "kibana.rac.alert.duration.us": 0,
+ "kibana.rac.alert.id": "opbeans-java",
+ "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
+ "kibana.rac.alert.status": "open",
+ "rule.category": "Test type",
+ "rule.id": "test_type",
+ "rule.name": "name",
+ "rule.uuid": "alertId",
+ "service.name": "opbeans-java",
+ "tags": Array [
+ "tags",
+ ],
+ },
+ Object {
+ "@timestamp": "2021-06-16T09:01:00.000Z",
+ "event.action": "open",
+ "event.kind": "signal",
+ "kibana.rac.alert.duration.us": 0,
+ "kibana.rac.alert.id": "opbeans-node",
+ "kibana.rac.alert.producer": "test",
+ "kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
+ "kibana.rac.alert.status": "open",
+ "rule.category": "Test type",
+ "rule.id": "test_type",
+ "rule.name": "name",
+ "rule.uuid": "alertId",
+ "service.name": "opbeans-node",
+ "tags": Array [
+ "tags",
+ ],
+ },
+ ]
+ `);
+ });
+ });
+
+ describe('when alerts are active', () => {
+ beforeEach(async () => {
+ await helpers.alertWithLifecycle([
+ {
+ id: 'opbeans-java',
+ fields: {
+ 'service.name': 'opbeans-java',
+ },
+ },
+ {
+ id: 'opbeans-node',
+ fields: {
+ 'service.name': 'opbeans-node',
+ },
+ },
+ ]);
+
+ await helpers.alertWithLifecycle([
+ {
+ id: 'opbeans-java',
+ fields: {
+ 'service.name': 'opbeans-java',
+ },
+ },
+ {
+ id: 'opbeans-node',
+ fields: {
+ 'service.name': 'opbeans-node',
+ },
+ },
+ ]);
+ });
+
+ it('writes the correct alerts', () => {
+ expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(2);
+
+ const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[1][0].body!;
+
+ const documents = body.filter((op: any) => !('index' in op)) as any[];
+
+ const evaluationDocuments = documents.filter((doc) => doc['event.kind'] === 'event');
+ const alertDocuments = documents.filter((doc) => doc['event.kind'] === 'signal');
+
+ expect(evaluationDocuments.length).toBe(2);
+ expect(alertDocuments.length).toBe(2);
+
+ expect(
+ alertDocuments.every((doc) => doc['kibana.rac.alert.status'] === 'open')
+ ).toBeTruthy();
+ expect(alertDocuments.every((doc) => doc['event.action'] === 'active')).toBeTruthy();
+
+ expect(alertDocuments.every((doc) => doc['kibana.rac.alert.duration.us'] > 0)).toBeTruthy();
+ });
+ });
+
+ describe('when alerts recover', () => {
+ beforeEach(async () => {
+ await helpers.alertWithLifecycle([
+ {
+ id: 'opbeans-java',
+ fields: {
+ 'service.name': 'opbeans-java',
+ },
+ },
+ {
+ id: 'opbeans-node',
+ fields: {
+ 'service.name': 'opbeans-node',
+ },
+ },
+ ]);
+
+ const lastOpbeansNodeDoc = helpers.ruleDataClientMock
+ .getWriter()
+ .bulk.mock.calls[0][0].body?.concat()
+ .reverse()
+ .find(
+ (doc: any) => !('index' in doc) && doc['service.name'] === 'opbeans-node'
+ ) as Record;
+
+ const stored = mapValues(lastOpbeansNodeDoc, (val) => {
+ return castArray(val);
+ });
+
+ helpers.ruleDataClientMock.getReader().search.mockResolvedValueOnce({
+ hits: {
+ hits: [{ fields: stored } as any],
+ total: {
+ value: 1,
+ relation: 'eq',
+ },
+ },
+ took: 0,
+ timed_out: false,
+ _shards: {
+ failed: 0,
+ successful: 1,
+ total: 1,
+ },
+ });
+
+ await helpers.alertWithLifecycle([
+ {
+ id: 'opbeans-java',
+ fields: {
+ 'service.name': 'opbeans-java',
+ },
+ },
+ ]);
+ });
+
+ it('writes the correct alerts', () => {
+ expect(helpers.ruleDataClientMock.getWriter().bulk).toHaveBeenCalledTimes(2);
+
+ const body = helpers.ruleDataClientMock.getWriter().bulk.mock.calls[1][0].body!;
+
+ const documents = body.filter((op: any) => !('index' in op)) as any[];
+
+ const opbeansJavaAlertDoc = documents.find(
+ (doc) => castArray(doc['service.name'])[0] === 'opbeans-java'
+ );
+ const opbeansNodeAlertDoc = documents.find(
+ (doc) => castArray(doc['service.name'])[0] === 'opbeans-node'
+ );
+
+ expect(opbeansJavaAlertDoc['event.action']).toBe('active');
+ expect(opbeansJavaAlertDoc['kibana.rac.alert.status']).toBe('open');
+
+ expect(opbeansNodeAlertDoc['event.action']).toBe('close');
+ expect(opbeansNodeAlertDoc['kibana.rac.alert.status']).toBe('closed');
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
index b523dd6770b9f..c2e0ae7c151ca 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
@@ -32,7 +32,7 @@ import { AlertTypeWithExecutor } from '../types';
import { ParsedTechnicalFields, parseTechnicalFields } from '../../common/parse_technical_fields';
import { getRuleExecutorData } from './get_rule_executor_data';
-type LifecycleAlertService> = (alert: {
+export type LifecycleAlertService> = (alert: {
id: string;
fields: Record;
}) => AlertInstance;
@@ -179,7 +179,7 @@ export const createLifecycleRuleTypeFactory: CreateLifecycleRuleTypeFactory = ({
...alertData,
...ruleExecutorData,
[TIMESTAMP]: timestamp,
- [EVENT_KIND]: 'state',
+ [EVENT_KIND]: 'event',
[ALERT_ID]: alertId,
};
@@ -221,8 +221,29 @@ export const createLifecycleRuleTypeFactory: CreateLifecycleRuleTypeFactory = ({
});
if (eventsToIndex.length) {
+ const alertEvents: Map = new Map();
+
+ for (const event of eventsToIndex) {
+ const uuid = event[ALERT_UUID]!;
+ let storedEvent = alertEvents.get(uuid);
+ if (!storedEvent) {
+ storedEvent = event;
+ }
+ alertEvents.set(uuid, {
+ ...storedEvent,
+ [EVENT_KIND]: 'signal',
+ });
+ }
+
await ruleDataClient.getWriter().bulk({
- body: eventsToIndex.flatMap((event) => [{ index: {} }, event]),
+ body: eventsToIndex
+ .flatMap((event) => [{ index: {} }, event])
+ .concat(
+ Array.from(alertEvents.values()).flatMap((event) => [
+ { index: { _id: event[ALERT_UUID]! } },
+ event,
+ ])
+ ),
});
}
@@ -238,7 +259,7 @@ export const createLifecycleRuleTypeFactory: CreateLifecycleRuleTypeFactory = ({
);
return {
- wrapped: nextWrappedState,
+ wrapped: nextWrappedState ?? {},
trackedAlerts: nextTrackedAlerts,
};
},
diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
index 1f8d1144349dd..3c2e98cfdae47 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
@@ -8,6 +8,7 @@
import expect from '@kbn/expect';
import { merge, omit } from 'lodash';
import { format } from 'url';
+import { EVENT_KIND } from '@kbn/rule-data-utils/target/technical_field_names';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { registry } from '../../common/registry';
@@ -259,7 +260,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
index: ALERTS_INDEX_TARGET,
body: {
query: {
- match_all: {},
+ term: {
+ [EVENT_KIND]: 'signal',
+ },
},
size: 1,
sort: {
@@ -286,7 +289,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
index: ALERTS_INDEX_TARGET,
body: {
query: {
- match_all: {},
+ term: {
+ [EVENT_KIND]: 'signal',
+ },
},
size: 1,
sort: {
@@ -313,7 +318,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
index: ALERTS_INDEX_TARGET,
body: {
query: {
- match_all: {},
+ term: {
+ [EVENT_KIND]: 'signal',
+ },
},
size: 1,
sort: {
@@ -346,7 +353,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"open",
],
"event.kind": Array [
- "state",
+ "signal",
],
"kibana.rac.alert.duration.us": Array [
0,
@@ -416,7 +423,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"open",
],
"event.kind": Array [
- "state",
+ "signal",
],
"kibana.rac.alert.duration.us": Array [
0,
@@ -486,7 +493,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
index: ALERTS_INDEX_TARGET,
body: {
query: {
- match_all: {},
+ term: {
+ [EVENT_KIND]: 'signal',
+ },
},
size: 1,
sort: {
@@ -521,7 +530,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"close",
],
"event.kind": Array [
- "state",
+ "signal",
],
"kibana.rac.alert.evaluation.threshold": Array [
30,
From adc95c102356867d0d3885e0a2164199f14f1d24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Wed, 16 Jun 2021 14:32:22 -0400
Subject: [PATCH 42/98] [APM] Fixing time comparison types (#101423)
* fixing time comparison types
* fixing ts issues
* addressing PR comments
* addressing PR comments
* addressing PR comments
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../shared/time_comparison/index.test.tsx | 374 ++++++++++--------
.../shared/time_comparison/index.tsx | 148 +++----
.../url_params_context/helpers.test.ts | 56 +++
.../context/url_params_context/helpers.ts | 30 +-
.../url_params_context/resolve_url_params.ts | 2 +-
.../context/url_params_context/types.ts | 2 +
.../url_params_context/url_params_context.tsx | 13 +-
7 files changed, 395 insertions(+), 230 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx
index a4f44290fe777..dd87c23908cbc 100644
--- a/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx
@@ -15,7 +15,7 @@ import {
expectTextsInDocument,
expectTextsNotInDocument,
} from '../../../utils/testHelpers';
-import { TimeComparison } from './';
+import { getComparisonTypes, getSelectOptions, TimeComparison } from './';
import * as urlHelpers from '../../shared/Links/url_helpers';
import moment from 'moment';
import { TimeRangeComparisonType } from './get_time_range_comparison';
@@ -37,188 +37,248 @@ describe('TimeComparison', () => {
moment.tz.setDefault('Europe/Amsterdam');
});
afterAll(() => moment.tz.setDefault(''));
- const spy = jest.spyOn(urlHelpers, 'replace');
- beforeEach(() => {
- jest.resetAllMocks();
- });
- describe('Time range is between 0 - 24 hours', () => {
- it('sets default values', () => {
- const Wrapper = getWrapper({
- start: '2021-01-28T14:45:00.000Z',
- end: '2021-01-28T15:00:00.000Z',
- rangeTo: 'now',
- });
- render( , {
- wrapper: Wrapper,
- });
- expect(spy).toHaveBeenCalledWith(expect.anything(), {
- query: {
- comparisonEnabled: 'true',
- comparisonType: TimeRangeComparisonType.DayBefore,
- },
- });
+
+ describe('getComparisonTypes', () => {
+ it('shows week and day before when 15 minutes is selected', () => {
+ expect(
+ getComparisonTypes({
+ start: '2021-06-04T16:17:02.335Z',
+ end: '2021-06-04T16:32:02.335Z',
+ })
+ ).toEqual([
+ TimeRangeComparisonType.DayBefore.valueOf(),
+ TimeRangeComparisonType.WeekBefore.valueOf(),
+ ]);
});
- it('selects day before and enables comparison', () => {
- const Wrapper = getWrapper({
- start: '2021-01-28T14:45:00.000Z',
- end: '2021-01-28T15:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.DayBefore,
- rangeTo: 'now',
- });
- const component = render( , {
- wrapper: Wrapper,
- });
- expectTextsInDocument(component, ['Day before', 'Week before']);
+
+ it('shows week and day before when Today is selected', () => {
expect(
- (component.getByTestId('comparisonSelect') as HTMLSelectElement)
- .selectedIndex
- ).toEqual(0);
+ getComparisonTypes({
+ start: '2021-06-04T04:00:00.000Z',
+ end: '2021-06-05T03:59:59.999Z',
+ })
+ ).toEqual([
+ TimeRangeComparisonType.DayBefore.valueOf(),
+ TimeRangeComparisonType.WeekBefore.valueOf(),
+ ]);
});
- it('enables yesterday option when date difference is equal to 24 hours', () => {
- const Wrapper = getWrapper({
- start: '2021-01-28T10:00:00.000Z',
- end: '2021-01-29T10:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.DayBefore,
- rangeTo: 'now',
- });
- const component = render( , {
- wrapper: Wrapper,
- });
- expectTextsInDocument(component, ['Day before', 'Week before']);
+ it('shows week and day before when 24 hours is selected', () => {
+ expect(
+ getComparisonTypes({
+ start: '2021-06-03T16:31:35.748Z',
+ end: '2021-06-04T16:31:35.748Z',
+ })
+ ).toEqual([
+ TimeRangeComparisonType.DayBefore.valueOf(),
+ TimeRangeComparisonType.WeekBefore.valueOf(),
+ ]);
+ });
+ it('shows week before when 25 hours is selected', () => {
expect(
- (component.getByTestId('comparisonSelect') as HTMLSelectElement)
- .selectedIndex
- ).toEqual(0);
+ getComparisonTypes({
+ start: '2021-06-02T12:32:00.000Z',
+ end: '2021-06-03T13:32:09.079Z',
+ })
+ ).toEqual([TimeRangeComparisonType.WeekBefore.valueOf()]);
});
- it('selects previous period when rangeTo is different than now', () => {
- const Wrapper = getWrapper({
- start: '2021-01-28T10:00:00.000Z',
- end: '2021-01-29T10:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.PeriodBefore,
- rangeTo: 'now-15m',
- });
- const component = render( , {
- wrapper: Wrapper,
- });
- expectTextsInDocument(component, ['27/01 11:00 - 28/01 11:00']);
+ it('shows week before when 7 days is selected', () => {
+ expect(
+ getComparisonTypes({
+ start: '2021-05-28T16:32:17.520Z',
+ end: '2021-06-04T16:32:17.520Z',
+ })
+ ).toEqual([TimeRangeComparisonType.WeekBefore.valueOf()]);
+ });
+ it('shows period before when 8 days is selected', () => {
expect(
- (component.getByTestId('comparisonSelect') as HTMLSelectElement)
- .selectedIndex
- ).toEqual(0);
+ getComparisonTypes({
+ start: '2021-05-27T16:32:46.747Z',
+ end: '2021-06-04T16:32:46.747Z',
+ })
+ ).toEqual([TimeRangeComparisonType.PeriodBefore.valueOf()]);
});
});
- describe('Time range is between 24 hours - 1 week', () => {
- it("doesn't show yesterday option when date difference is greater than 24 hours", () => {
- const Wrapper = getWrapper({
- start: '2021-01-28T10:00:00.000Z',
- end: '2021-01-29T11:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.WeekBefore,
- rangeTo: 'now',
- });
- const component = render( , {
- wrapper: Wrapper,
- });
- expectTextsNotInDocument(component, ['Day before']);
- expectTextsInDocument(component, ['Week before']);
- });
- it('sets default values', () => {
- const Wrapper = getWrapper({
- start: '2021-01-26T15:00:00.000Z',
- end: '2021-01-28T15:00:00.000Z',
- rangeTo: 'now',
- });
- render( , {
- wrapper: Wrapper,
- });
- expect(spy).toHaveBeenCalledWith(expect.anything(), {
- query: {
- comparisonEnabled: 'true',
- comparisonType: TimeRangeComparisonType.WeekBefore,
+ describe('getSelectOptions', () => {
+ it('returns formatted text based on comparison type', () => {
+ expect(
+ getSelectOptions({
+ comparisonTypes: [
+ TimeRangeComparisonType.DayBefore,
+ TimeRangeComparisonType.WeekBefore,
+ TimeRangeComparisonType.PeriodBefore,
+ ],
+ start: '2021-05-27T16:32:46.747Z',
+ end: '2021-06-04T16:32:46.747Z',
+ })
+ ).toEqual([
+ {
+ value: TimeRangeComparisonType.DayBefore.valueOf(),
+ text: 'Day before',
},
- });
+ {
+ value: TimeRangeComparisonType.WeekBefore.valueOf(),
+ text: 'Week before',
+ },
+ {
+ value: TimeRangeComparisonType.PeriodBefore.valueOf(),
+ text: '19/05 18:32 - 27/05 18:32',
+ },
+ ]);
});
- it('selects week and enables comparison', () => {
- const Wrapper = getWrapper({
- start: '2021-01-26T15:00:00.000Z',
- end: '2021-01-28T15:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.WeekBefore,
- rangeTo: 'now',
- });
- const component = render( , {
- wrapper: Wrapper,
- });
- expectTextsNotInDocument(component, ['Day before']);
- expectTextsInDocument(component, ['Week before']);
+
+ it('formats period before as DD/MM/YY HH:mm when range years are different', () => {
expect(
- (component.getByTestId('comparisonSelect') as HTMLSelectElement)
- .selectedIndex
- ).toEqual(0);
+ getSelectOptions({
+ comparisonTypes: [TimeRangeComparisonType.PeriodBefore],
+ start: '2020-05-27T16:32:46.747Z',
+ end: '2021-06-04T16:32:46.747Z',
+ })
+ ).toEqual([
+ {
+ value: TimeRangeComparisonType.PeriodBefore.valueOf(),
+ text: '20/05/19 18:32 - 27/05/20 18:32',
+ },
+ ]);
});
+ });
- it('selects previous period when rangeTo is different than now', () => {
- const Wrapper = getWrapper({
- start: '2021-01-26T15:00:00.000Z',
- end: '2021-01-28T15:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.PeriodBefore,
- rangeTo: '2021-01-28T15:00:00.000Z',
+ describe('TimeComparison component', () => {
+ const spy = jest.spyOn(urlHelpers, 'replace');
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+ describe('Time range is between 0 - 24 hours', () => {
+ it('sets default values', () => {
+ const Wrapper = getWrapper({
+ exactStart: '2021-06-04T16:17:02.335Z',
+ exactEnd: '2021-06-04T16:32:02.335Z',
+ });
+ render( , { wrapper: Wrapper });
+ expect(spy).toHaveBeenCalledWith(expect.anything(), {
+ query: {
+ comparisonEnabled: 'true',
+ comparisonType: TimeRangeComparisonType.DayBefore,
+ },
+ });
});
- const component = render( , {
- wrapper: Wrapper,
+ it('selects day before and enables comparison', () => {
+ const Wrapper = getWrapper({
+ exactStart: '2021-06-04T16:17:02.335Z',
+ exactEnd: '2021-06-04T16:32:02.335Z',
+ comparisonEnabled: true,
+ comparisonType: TimeRangeComparisonType.DayBefore,
+ });
+ const component = render( , { wrapper: Wrapper });
+ expectTextsInDocument(component, ['Day before', 'Week before']);
+ expect(
+ (component.getByTestId('comparisonSelect') as HTMLSelectElement)
+ .selectedIndex
+ ).toEqual(0);
+ });
+
+ it('enables day before option when date difference is equal to 24 hours', () => {
+ const Wrapper = getWrapper({
+ exactStart: '2021-06-03T16:31:35.748Z',
+ exactEnd: '2021-06-04T16:31:35.748Z',
+ comparisonEnabled: true,
+ comparisonType: TimeRangeComparisonType.DayBefore,
+ });
+ const component = render( , { wrapper: Wrapper });
+ expectTextsInDocument(component, ['Day before', 'Week before']);
+ expect(
+ (component.getByTestId('comparisonSelect') as HTMLSelectElement)
+ .selectedIndex
+ ).toEqual(0);
});
- expectTextsInDocument(component, ['24/01 16:00 - 26/01 16:00']);
- expect(
- (component.getByTestId('comparisonSelect') as HTMLSelectElement)
- .selectedIndex
- ).toEqual(0);
});
- });
- describe('Time range is greater than 7 days', () => {
- it('Shows absolute times without year when within the same year', () => {
- const Wrapper = getWrapper({
- start: '2021-01-20T15:00:00.000Z',
- end: '2021-01-28T15:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.PeriodBefore,
- rangeTo: 'now',
+ describe('Time range is between 24 hours - 1 week', () => {
+ it("doesn't show day before option when date difference is greater than 24 hours", () => {
+ const Wrapper = getWrapper({
+ exactStart: '2021-06-02T12:32:00.000Z',
+ exactEnd: '2021-06-03T13:32:09.079Z',
+ comparisonEnabled: true,
+ comparisonType: TimeRangeComparisonType.WeekBefore,
+ });
+ const component = render( , {
+ wrapper: Wrapper,
+ });
+ expectTextsNotInDocument(component, ['Day before']);
+ expectTextsInDocument(component, ['Week before']);
});
- const component = render( , {
- wrapper: Wrapper,
+ it('sets default values', () => {
+ const Wrapper = getWrapper({
+ exactStart: '2021-06-02T12:32:00.000Z',
+ exactEnd: '2021-06-03T13:32:09.079Z',
+ });
+ render( , {
+ wrapper: Wrapper,
+ });
+ expect(spy).toHaveBeenCalledWith(expect.anything(), {
+ query: {
+ comparisonEnabled: 'true',
+ comparisonType: TimeRangeComparisonType.WeekBefore,
+ },
+ });
+ });
+ it('selects week before and enables comparison', () => {
+ const Wrapper = getWrapper({
+ exactStart: '2021-06-02T12:32:00.000Z',
+ exactEnd: '2021-06-03T13:32:09.079Z',
+ comparisonEnabled: true,
+ comparisonType: TimeRangeComparisonType.WeekBefore,
+ });
+ const component = render( , {
+ wrapper: Wrapper,
+ });
+ expectTextsNotInDocument(component, ['Day before']);
+ expectTextsInDocument(component, ['Week before']);
+ expect(
+ (component.getByTestId('comparisonSelect') as HTMLSelectElement)
+ .selectedIndex
+ ).toEqual(0);
});
- expect(spy).not.toHaveBeenCalled();
- expectTextsInDocument(component, ['12/01 16:00 - 20/01 16:00']);
- expect(
- (component.getByTestId('comparisonSelect') as HTMLSelectElement)
- .selectedIndex
- ).toEqual(0);
});
- it('Shows absolute times with year when on different year', () => {
- const Wrapper = getWrapper({
- start: '2020-12-20T15:00:00.000Z',
- end: '2021-01-28T15:00:00.000Z',
- comparisonEnabled: true,
- comparisonType: TimeRangeComparisonType.PeriodBefore,
- rangeTo: 'now',
+ describe('Time range is greater than 7 days', () => {
+ it('Shows absolute times without year when within the same year', () => {
+ const Wrapper = getWrapper({
+ exactStart: '2021-05-27T16:32:46.747Z',
+ exactEnd: '2021-06-04T16:32:46.747Z',
+ comparisonEnabled: true,
+ comparisonType: TimeRangeComparisonType.PeriodBefore,
+ });
+ const component = render( , {
+ wrapper: Wrapper,
+ });
+ expect(spy).not.toHaveBeenCalled();
+ expectTextsInDocument(component, ['19/05 18:32 - 27/05 18:32']);
+ expect(
+ (component.getByTestId('comparisonSelect') as HTMLSelectElement)
+ .selectedIndex
+ ).toEqual(0);
});
- const component = render( , {
- wrapper: Wrapper,
+
+ it('Shows absolute times with year when on different year', () => {
+ const Wrapper = getWrapper({
+ exactStart: '2020-05-27T16:32:46.747Z',
+ exactEnd: '2021-06-04T16:32:46.747Z',
+ comparisonEnabled: true,
+ comparisonType: TimeRangeComparisonType.PeriodBefore,
+ });
+ const component = render( , {
+ wrapper: Wrapper,
+ });
+ expect(spy).not.toHaveBeenCalled();
+ expectTextsInDocument(component, ['20/05/19 18:32 - 27/05/20 18:32']);
+ expect(
+ (component.getByTestId('comparisonSelect') as HTMLSelectElement)
+ .selectedIndex
+ ).toEqual(0);
});
- expect(spy).not.toHaveBeenCalled();
- expectTextsInDocument(component, ['11/11/20 16:00 - 20/12/20 16:00']);
- expect(
- (component.getByTestId('comparisonSelect') as HTMLSelectElement)
- .selectedIndex
- ).toEqual(0);
});
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx
index 98fbd4f399d98..cbe7b81486a64 100644
--- a/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.tsx
@@ -59,80 +59,92 @@ function formatDate({
return `${momentStart.format(dateFormat)} - ${momentEnd.format(dateFormat)}`;
}
-function getSelectOptions({
+export function getComparisonTypes({
start,
end,
- rangeTo,
- comparisonEnabled,
}: {
start?: string;
end?: string;
- rangeTo?: string;
- comparisonEnabled?: boolean;
}) {
const momentStart = moment(start);
const momentEnd = moment(end);
- const dayBeforeOption = {
- value: TimeRangeComparisonType.DayBefore,
- text: i18n.translate('xpack.apm.timeComparison.select.dayBefore', {
- defaultMessage: 'Day before',
- }),
- };
-
- const weekBeforeOption = {
- value: TimeRangeComparisonType.WeekBefore,
- text: i18n.translate('xpack.apm.timeComparison.select.weekBefore', {
- defaultMessage: 'Week before',
- }),
- };
-
- const dateDiff = Number(
- getDateDifference({
- start: momentStart,
- end: momentEnd,
- unitOfTime: 'days',
- precise: true,
- }).toFixed(2)
- );
-
- const isRangeToNow = rangeTo === 'now';
+ const dateDiff = getDateDifference({
+ start: momentStart,
+ end: momentEnd,
+ unitOfTime: 'days',
+ precise: true,
+ });
- if (isRangeToNow) {
- // Less than or equals to one day
- if (dateDiff <= 1) {
- return [dayBeforeOption, weekBeforeOption];
- }
+ // Less than or equals to one day
+ if (dateDiff <= 1) {
+ return [
+ TimeRangeComparisonType.DayBefore,
+ TimeRangeComparisonType.WeekBefore,
+ ];
+ }
- // Less than or equals to one week
- if (dateDiff <= 7) {
- return [weekBeforeOption];
- }
+ // Less than or equals to one week
+ if (dateDiff <= 7) {
+ return [TimeRangeComparisonType.WeekBefore];
}
+ // }
- const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
- comparisonType: TimeRangeComparisonType.PeriodBefore,
- start,
- end,
- comparisonEnabled,
- });
+ // above one week or when rangeTo is not "now"
+ return [TimeRangeComparisonType.PeriodBefore];
+}
- const dateFormat = getDateFormat({
- previousPeriodStart: comparisonStart,
- currentPeriodEnd: end,
- });
+export function getSelectOptions({
+ comparisonTypes,
+ start,
+ end,
+}: {
+ comparisonTypes: TimeRangeComparisonType[];
+ start?: string;
+ end?: string;
+}) {
+ return comparisonTypes.map((value) => {
+ switch (value) {
+ case TimeRangeComparisonType.DayBefore: {
+ return {
+ value,
+ text: i18n.translate('xpack.apm.timeComparison.select.dayBefore', {
+ defaultMessage: 'Day before',
+ }),
+ };
+ }
+ case TimeRangeComparisonType.WeekBefore: {
+ return {
+ value,
+ text: i18n.translate('xpack.apm.timeComparison.select.weekBefore', {
+ defaultMessage: 'Week before',
+ }),
+ };
+ }
+ case TimeRangeComparisonType.PeriodBefore: {
+ const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
+ comparisonType: TimeRangeComparisonType.PeriodBefore,
+ start,
+ end,
+ comparisonEnabled: true,
+ });
- const prevPeriodOption = {
- value: TimeRangeComparisonType.PeriodBefore,
- text: formatDate({
- dateFormat,
- previousPeriodStart: comparisonStart,
- previousPeriodEnd: comparisonEnd,
- }),
- };
+ const dateFormat = getDateFormat({
+ previousPeriodStart: comparisonStart,
+ currentPeriodEnd: end,
+ });
- // above one week or when rangeTo is not "now"
- return [prevPeriodOption];
+ return {
+ value,
+ text: formatDate({
+ dateFormat,
+ previousPeriodStart: comparisonStart,
+ previousPeriodEnd: comparisonEnd,
+ }),
+ };
+ }
+ }
+ });
}
export function TimeComparison() {
@@ -140,14 +152,12 @@ export function TimeComparison() {
const history = useHistory();
const { isMedium, isLarge } = useBreakPoints();
const {
- urlParams: { start, end, comparisonEnabled, comparisonType, rangeTo },
+ urlParams: { comparisonEnabled, comparisonType, exactStart, exactEnd },
} = useUrlParams();
- const selectOptions = getSelectOptions({
- start,
- end,
- rangeTo,
- comparisonEnabled,
+ const comparisonTypes = getComparisonTypes({
+ start: exactStart,
+ end: exactEnd,
});
// Sets default values
@@ -155,14 +165,18 @@ export function TimeComparison() {
urlHelpers.replace(history, {
query: {
comparisonEnabled: comparisonEnabled === false ? 'false' : 'true',
- comparisonType: comparisonType
- ? comparisonType
- : selectOptions[0].value,
+ comparisonType: comparisonType ? comparisonType : comparisonTypes[0],
},
});
return null;
}
+ const selectOptions = getSelectOptions({
+ comparisonTypes,
+ start: exactStart,
+ end: exactEnd,
+ });
+
const isSelectedComparisonTypeAvailable = selectOptions.some(
({ value }) => value === comparisonType
);
diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts
index 4de68a5bc2036..784b10b3f3ee1 100644
--- a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts
+++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts
@@ -10,6 +10,9 @@ import moment from 'moment-timezone';
import * as helpers from './helpers';
describe('url_params_context helpers', () => {
+ beforeEach(() => {
+ jest.restoreAllMocks();
+ });
describe('getDateRange', () => {
describe('with non-rounded dates', () => {
describe('one minute', () => {
@@ -23,6 +26,8 @@ describe('url_params_context helpers', () => {
).toEqual({
start: '2021-01-28T05:47:00.000Z',
end: '2021-01-28T05:48:55.304Z',
+ exactStart: '2021-01-28T05:47:52.134Z',
+ exactEnd: '2021-01-28T05:48:55.304Z',
});
});
});
@@ -37,6 +42,8 @@ describe('url_params_context helpers', () => {
).toEqual({
start: '2021-01-27T05:46:00.000Z',
end: '2021-01-28T05:46:13.367Z',
+ exactStart: '2021-01-27T05:46:07.377Z',
+ exactEnd: '2021-01-28T05:46:13.367Z',
});
});
});
@@ -52,6 +59,8 @@ describe('url_params_context helpers', () => {
).toEqual({
start: '2020-01-28T05:52:00.000Z',
end: '2021-01-28T05:52:39.741Z',
+ exactStart: '2020-01-28T05:52:36.290Z',
+ exactEnd: '2021-01-28T05:52:39.741Z',
});
});
});
@@ -66,6 +75,8 @@ describe('url_params_context helpers', () => {
rangeTo: 'now',
start: '1970-01-01T00:00:00.000Z',
end: '1971-01-01T00:00:00.000Z',
+ exactStart: '1970-01-01T00:00:00.000Z',
+ exactEnd: '1971-01-01T00:00:00.000Z',
},
rangeFrom: 'now-1m',
rangeTo: 'now',
@@ -73,6 +84,8 @@ describe('url_params_context helpers', () => {
).toEqual({
start: '1970-01-01T00:00:00.000Z',
end: '1971-01-01T00:00:00.000Z',
+ exactStart: '1970-01-01T00:00:00.000Z',
+ exactEnd: '1971-01-01T00:00:00.000Z',
});
});
});
@@ -94,24 +107,37 @@ describe('url_params_context helpers', () => {
).toEqual({
start: '1972-01-01T00:00:00.000Z',
end: '1973-01-01T00:00:00.000Z',
+ exactStart: undefined,
+ exactEnd: undefined,
});
});
});
describe('when the start or end are invalid', () => {
it('returns the previous state', () => {
+ const endDate = moment('2021-06-04T18:03:24.211Z');
+ jest
+ .spyOn(datemath, 'parse')
+ .mockReturnValueOnce(undefined)
+ .mockReturnValueOnce(endDate)
+ .mockReturnValueOnce(undefined)
+ .mockReturnValueOnce(endDate);
expect(
helpers.getDateRange({
state: {
start: '1972-01-01T00:00:00.000Z',
end: '1973-01-01T00:00:00.000Z',
+ exactStart: '1972-01-01T00:00:00.000Z',
+ exactEnd: '1973-01-01T00:00:00.000Z',
},
rangeFrom: 'nope',
rangeTo: 'now',
})
).toEqual({
start: '1972-01-01T00:00:00.000Z',
+ exactStart: '1972-01-01T00:00:00.000Z',
end: '1973-01-01T00:00:00.000Z',
+ exactEnd: '1973-01-01T00:00:00.000Z',
});
});
});
@@ -134,8 +160,38 @@ describe('url_params_context helpers', () => {
).toEqual({
start: '1970-01-01T00:00:00.000Z',
end: '1970-01-01T00:00:00.000Z',
+ exactStart: '1970-01-01T00:00:00.000Z',
+ exactEnd: '1970-01-01T00:00:00.000Z',
});
});
});
});
+
+ describe('getExactDate', () => {
+ it('returns date when it is not not relative', () => {
+ expect(helpers.getExactDate('2021-01-28T05:47:52.134Z')).toEqual(
+ new Date('2021-01-28T05:47:52.134Z')
+ );
+ });
+
+ ['s', 'm', 'h', 'd', 'w'].map((roundingOption) =>
+ it(`removes /${roundingOption} rounding option from relative time`, () => {
+ const spy = jest.spyOn(datemath, 'parse');
+ helpers.getExactDate(`now/${roundingOption}`);
+ expect(spy).toHaveBeenCalledWith('now', {});
+ })
+ );
+
+ it('removes rounding option but keeps subtracting time', () => {
+ const spy = jest.spyOn(datemath, 'parse');
+ helpers.getExactDate('now-24h/h');
+ expect(spy).toHaveBeenCalledWith('now-24h', {});
+ });
+
+ it('removes rounding option but keeps adding time', () => {
+ const spy = jest.spyOn(datemath, 'parse');
+ helpers.getExactDate('now+15m/h');
+ expect(spy).toHaveBeenCalledWith('now+15m', {});
+ });
+ });
});
diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts
index eae9eba8b3dda..902456bf4ebc0 100644
--- a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts
+++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts
@@ -19,6 +19,16 @@ function getParsedDate(rawDate?: string, options = {}) {
}
}
+export function getExactDate(rawDate: string) {
+ const isRelativeDate = rawDate.startsWith('now');
+ if (isRelativeDate) {
+ // remove rounding from relative dates "Today" (now/d) and "This week" (now/w)
+ const rawDateWithouRounding = rawDate.replace(/\/([smhdw])$/, '');
+ return getParsedDate(rawDateWithouRounding);
+ }
+ return getParsedDate(rawDate);
+}
+
export function getDateRange({
state,
rangeFrom,
@@ -30,16 +40,28 @@ export function getDateRange({
}) {
// If the previous state had the same range, just return that instead of calculating a new range.
if (state.rangeFrom === rangeFrom && state.rangeTo === rangeTo) {
- return { start: state.start, end: state.end };
+ return {
+ start: state.start,
+ end: state.end,
+ exactStart: state.exactStart,
+ exactEnd: state.exactEnd,
+ };
}
-
const start = getParsedDate(rangeFrom);
const end = getParsedDate(rangeTo, { roundUp: true });
+ const exactStart = rangeFrom ? getExactDate(rangeFrom) : undefined;
+ const exactEnd = rangeTo ? getExactDate(rangeTo) : undefined;
+
// `getParsedDate` will return undefined for invalid or empty dates. We return
// the previous state if either date is undefined.
if (!start || !end) {
- return { start: state.start, end: state.end };
+ return {
+ start: state.start,
+ end: state.end,
+ exactStart: state.exactStart,
+ exactEnd: state.exactEnd,
+ };
}
// rounds down start to minute
@@ -48,6 +70,8 @@ export function getDateRange({
return {
start: roundedStart.toISOString(),
end: end.toISOString(),
+ exactStart: exactStart?.toISOString(),
+ exactEnd: exactEnd?.toISOString(),
};
}
diff --git a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts
index b6e7330be30cb..134f65bbf0f40 100644
--- a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts
+++ b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts
@@ -24,7 +24,7 @@ import { IUrlParams } from './types';
type TimeUrlParams = Pick<
IUrlParams,
- 'start' | 'end' | 'rangeFrom' | 'rangeTo'
+ 'start' | 'end' | 'rangeFrom' | 'rangeTo' | 'exactStart' | 'exactEnd'
>;
export function resolveUrlParams(location: Location, state: TimeUrlParams) {
diff --git a/x-pack/plugins/apm/public/context/url_params_context/types.ts b/x-pack/plugins/apm/public/context/url_params_context/types.ts
index 4332019d1a1c9..5e9e4bd257b87 100644
--- a/x-pack/plugins/apm/public/context/url_params_context/types.ts
+++ b/x-pack/plugins/apm/public/context/url_params_context/types.ts
@@ -17,6 +17,8 @@ export type IUrlParams = {
environment?: string;
rangeFrom?: string;
rangeTo?: string;
+ exactStart?: string;
+ exactEnd?: string;
refreshInterval?: number;
refreshPaused?: boolean;
sortDirection?: string;
diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx
index 1da57ac10a20c..f3969745b6ea7 100644
--- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx
+++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx
@@ -54,7 +54,14 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter(
({ location, children }) => {
const refUrlParams = useRef(resolveUrlParams(location, {}));
- const { start, end, rangeFrom, rangeTo } = refUrlParams.current;
+ const {
+ start,
+ end,
+ rangeFrom,
+ rangeTo,
+ exactStart,
+ exactEnd,
+ } = refUrlParams.current;
// Counter to force an update in useFetcher when the refresh button is clicked.
const [rangeId, setRangeId] = useState(0);
@@ -66,8 +73,10 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter(
end,
rangeFrom,
rangeTo,
+ exactStart,
+ exactEnd,
}),
- [location, start, end, rangeFrom, rangeTo]
+ [location, start, end, rangeFrom, rangeTo, exactStart, exactEnd]
);
refUrlParams.current = urlParams;
From ab2a80f4b0f830947e57245fcfe2961a11d7ecd3 Mon Sep 17 00:00:00 2001
From: Chris Roberson
Date: Wed, 16 Jun 2021 15:20:28 -0400
Subject: [PATCH 43/98] [Task Manager] Log at different levels based on the
state (#101751)
* Log at different levels based on the state
* Fix types and add tests
* Remove unnecessary code
* Add more descriptive message
* Partially fix failing tests
* Move into separate function
* Get rid of customStatus in favor of moving the logging logic to a separate, mockable function
* Remove debug logging
* Do not log as an error if the stats are empty
* PR feedback
* Add docker whitelist
* alpha order
* English is hard
* Removing extra newline
* PR feedback around ignoring capacity estimation
* Move json utils
---
docs/settings/task-manager-settings.asciidoc | 3 +
.../resources/base/bin/kibana-docker | 1 +
.../task_manager/server/config.test.ts | 3 +
x-pack/plugins/task_manager/server/config.ts | 5 +
.../managed_configuration.test.ts | 1 +
.../lib/calculate_health_status.mock.ts | 14 +
.../server/lib/calculate_health_status.ts | 79 ++++++
.../server/lib/log_health_metrics.mock.ts | 14 +
.../server/lib/log_health_metrics.test.ts | 262 ++++++++++++++++++
.../server/lib/log_health_metrics.ts | 47 ++++
.../configuration_statistics.test.ts | 1 +
.../monitoring_stats_stream.test.ts | 1 +
.../monitoring/monitoring_stats_stream.ts | 1 -
.../task_manager/server/plugin.test.ts | 2 +
.../server/polling_lifecycle.test.ts | 1 +
.../task_manager/server/routes/health.test.ts | 141 +++++++++-
.../task_manager/server/routes/health.ts | 80 +-----
17 files changed, 576 insertions(+), 80 deletions(-)
create mode 100644 x-pack/plugins/task_manager/server/lib/calculate_health_status.mock.ts
create mode 100644 x-pack/plugins/task_manager/server/lib/calculate_health_status.ts
create mode 100644 x-pack/plugins/task_manager/server/lib/log_health_metrics.mock.ts
create mode 100644 x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
create mode 100644 x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
diff --git a/docs/settings/task-manager-settings.asciidoc b/docs/settings/task-manager-settings.asciidoc
index 12c958c9e8683..87f5b700870eb 100644
--- a/docs/settings/task-manager-settings.asciidoc
+++ b/docs/settings/task-manager-settings.asciidoc
@@ -28,6 +28,9 @@ Task Manager runs background tasks by polling for work on an interval. You can
| `xpack.task_manager.max_workers`
| The maximum number of tasks that this Kibana instance will run simultaneously. Defaults to 10.
Starting in 8.0, it will not be possible to set the value greater than 100.
+
+ | `xpack.task_manager.monitored_stats_warn_delayed_task_start_in_seconds`
+ | The amount of seconds we allow a task to delay before printing a warning server log. Defaults to 60.
|===
[float]
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
index a1838c571ea0b..f82a21c2f520c 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
@@ -322,6 +322,7 @@ kibana_vars=(
xpack.task_manager.monitored_aggregated_stats_refresh_rate
xpack.task_manager.monitored_stats_required_freshness
xpack.task_manager.monitored_stats_running_average_window
+ xpack.task_manager.monitored_stats_warn_delayed_task_start_in_seconds
xpack.task_manager.monitored_task_execution_thresholds
xpack.task_manager.poll_interval
xpack.task_manager.request_capacity
diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts
index 85a139956ae96..947b1fd84467e 100644
--- a/x-pack/plugins/task_manager/server/config.test.ts
+++ b/x-pack/plugins/task_manager/server/config.test.ts
@@ -20,6 +20,7 @@ describe('config validation', () => {
"monitored_aggregated_stats_refresh_rate": 60000,
"monitored_stats_required_freshness": 4000,
"monitored_stats_running_average_window": 50,
+ "monitored_stats_warn_delayed_task_start_in_seconds": 60,
"monitored_task_execution_thresholds": Object {
"custom": Object {},
"default": Object {
@@ -68,6 +69,7 @@ describe('config validation', () => {
"monitored_aggregated_stats_refresh_rate": 60000,
"monitored_stats_required_freshness": 4000,
"monitored_stats_running_average_window": 50,
+ "monitored_stats_warn_delayed_task_start_in_seconds": 60,
"monitored_task_execution_thresholds": Object {
"custom": Object {},
"default": Object {
@@ -103,6 +105,7 @@ describe('config validation', () => {
"monitored_aggregated_stats_refresh_rate": 60000,
"monitored_stats_required_freshness": 4000,
"monitored_stats_running_average_window": 50,
+ "monitored_stats_warn_delayed_task_start_in_seconds": 60,
"monitored_task_execution_thresholds": Object {
"custom": Object {
"alerting:always-fires": Object {
diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts
index 3ebfe7da7c3f9..5dee66cf113b2 100644
--- a/x-pack/plugins/task_manager/server/config.ts
+++ b/x-pack/plugins/task_manager/server/config.ts
@@ -18,6 +18,7 @@ export const DEFAULT_VERSION_CONFLICT_THRESHOLD = 80;
// Refresh aggregated monitored stats at a default rate of once a minute
export const DEFAULT_MONITORING_REFRESH_RATE = 60 * 1000;
export const DEFAULT_MONITORING_STATS_RUNNING_AVERGAE_WINDOW = 50;
+export const DEFAULT_MONITORING_STATS_WARN_DELAYED_TASK_START_IN_SECONDS = 60;
export const taskExecutionFailureThresholdSchema = schema.object(
{
@@ -109,6 +110,10 @@ export const configSchema = schema.object(
defaultValue: {},
}),
}),
+ /* The amount of seconds we allow a task to delay before printing a warning server log */
+ monitored_stats_warn_delayed_task_start_in_seconds: schema.number({
+ defaultValue: DEFAULT_MONITORING_STATS_WARN_DELAYED_TASK_START_IN_SECONDS,
+ }),
},
{
validate: (config) => {
diff --git a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts
index f7ea6cea53857..f6ee8d8a78ddc 100644
--- a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts
+++ b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts
@@ -37,6 +37,7 @@ describe('managed configuration', () => {
version_conflict_threshold: 80,
max_poll_inactivity_cycles: 10,
monitored_aggregated_stats_refresh_rate: 60000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
monitored_stats_required_freshness: 4000,
monitored_stats_running_average_window: 50,
request_capacity: 1000,
diff --git a/x-pack/plugins/task_manager/server/lib/calculate_health_status.mock.ts b/x-pack/plugins/task_manager/server/lib/calculate_health_status.mock.ts
new file mode 100644
index 0000000000000..f34a26560133b
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/calculate_health_status.mock.ts
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+const createCalculateHealthStatusMock = () => {
+ return jest.fn();
+};
+
+export const calculateHealthStatusMock = {
+ create: createCalculateHealthStatusMock,
+};
diff --git a/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts b/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts
new file mode 100644
index 0000000000000..7a6bc59862100
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { isString } from 'lodash';
+import { JsonValue } from '@kbn/common-utils';
+import { HealthStatus, RawMonitoringStats } from '../monitoring';
+import { TaskManagerConfig } from '../config';
+
+export function calculateHealthStatus(
+ summarizedStats: RawMonitoringStats,
+ config: TaskManagerConfig
+): HealthStatus {
+ const now = Date.now();
+
+ // if "hot" health stats are any more stale than monitored_stats_required_freshness (pollInterval +1s buffer by default)
+ // consider the system unhealthy
+ const requiredHotStatsFreshness: number = config.monitored_stats_required_freshness;
+
+ // if "cold" health stats are any more stale than the configured refresh (+ a buffer), consider the system unhealthy
+ const requiredColdStatsFreshness: number = config.monitored_aggregated_stats_refresh_rate * 1.5;
+
+ /**
+ * If the monitored stats aren't fresh, return a red status
+ */
+ const healthStatus =
+ hasStatus(summarizedStats.stats, HealthStatus.Error) ||
+ hasExpiredHotTimestamps(summarizedStats, now, requiredHotStatsFreshness) ||
+ hasExpiredColdTimestamps(summarizedStats, now, requiredColdStatsFreshness)
+ ? HealthStatus.Error
+ : hasStatus(summarizedStats.stats, HealthStatus.Warning)
+ ? HealthStatus.Warning
+ : HealthStatus.OK;
+ return healthStatus;
+}
+
+function hasStatus(stats: RawMonitoringStats['stats'], status: HealthStatus): boolean {
+ return Object.values(stats)
+ .map((stat) => stat?.status === status)
+ .includes(true);
+}
+
+/**
+ * If certain "hot" stats are not fresh, then the _health api will should return a Red status
+ * @param monitoringStats The monitored stats
+ * @param now The time to compare against
+ * @param requiredFreshness How fresh should these stats be
+ */
+function hasExpiredHotTimestamps(
+ monitoringStats: RawMonitoringStats,
+ now: number,
+ requiredFreshness: number
+): boolean {
+ const diff =
+ now -
+ getOldestTimestamp(
+ monitoringStats.last_update,
+ monitoringStats.stats.runtime?.value.polling.last_successful_poll
+ );
+ return diff > requiredFreshness;
+}
+
+function hasExpiredColdTimestamps(
+ monitoringStats: RawMonitoringStats,
+ now: number,
+ requiredFreshness: number
+): boolean {
+ return now - getOldestTimestamp(monitoringStats.stats.workload?.timestamp) > requiredFreshness;
+}
+
+function getOldestTimestamp(...timestamps: Array): number {
+ const validTimestamps = timestamps
+ .map((timestamp) => (isString(timestamp) ? Date.parse(timestamp) : NaN))
+ .filter((timestamp) => !isNaN(timestamp));
+ return validTimestamps.length ? Math.min(...validTimestamps) : 0;
+}
diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.mock.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.mock.ts
new file mode 100644
index 0000000000000..96c0f686ad61e
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.mock.ts
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+const createLogHealthMetricsMock = () => {
+ return jest.fn();
+};
+
+export const logHealthMetricsMock = {
+ create: createLogHealthMetricsMock,
+};
diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
new file mode 100644
index 0000000000000..ccbbf81ebfa31
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
@@ -0,0 +1,262 @@
+/*
+ * Copyright 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 { merge } from 'lodash';
+import { loggingSystemMock } from 'src/core/server/mocks';
+import { configSchema, TaskManagerConfig } from '../config';
+import { HealthStatus } from '../monitoring';
+import { TaskPersistence } from '../monitoring/task_run_statistics';
+import { MonitoredHealth } from '../routes/health';
+import { logHealthMetrics } from './log_health_metrics';
+import { Logger } from '../../../../../src/core/server';
+
+jest.mock('./calculate_health_status', () => ({
+ calculateHealthStatus: jest.fn(),
+}));
+
+describe('logHealthMetrics', () => {
+ afterEach(() => {
+ const { calculateHealthStatus } = jest.requireMock('./calculate_health_status');
+ (calculateHealthStatus as jest.Mock).mockReset();
+ });
+ it('should log as debug if status is OK', () => {
+ const logger = loggingSystemMock.create().get();
+ const config = getTaskManagerConfig({
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
+ });
+ const health = getMockMonitoredHealth();
+
+ logHealthMetrics(health, logger, config);
+
+ const firstDebug = JSON.parse(
+ (logger as jest.Mocked).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
+ );
+ expect(firstDebug).toMatchObject(health);
+ });
+
+ it('should log as warn if status is Warn', () => {
+ const logger = loggingSystemMock.create().get();
+ const config = getTaskManagerConfig({
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
+ });
+ const health = getMockMonitoredHealth();
+ const { calculateHealthStatus } = jest.requireMock('./calculate_health_status');
+ (calculateHealthStatus as jest.Mock).mockImplementation(
+ () => HealthStatus.Warning
+ );
+
+ logHealthMetrics(health, logger, config);
+
+ const logMessage = JSON.parse(
+ ((logger as jest.Mocked).warn.mock.calls[0][0] as string).replace(
+ 'Latest Monitored Stats: ',
+ ''
+ )
+ );
+ expect(logMessage).toMatchObject(health);
+ });
+
+ it('should log as error if status is Error', () => {
+ const logger = loggingSystemMock.create().get();
+ const config = getTaskManagerConfig({
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
+ });
+ const health = getMockMonitoredHealth();
+ const { calculateHealthStatus } = jest.requireMock('./calculate_health_status');
+ (calculateHealthStatus as jest.Mock).mockImplementation(() => HealthStatus.Error);
+
+ logHealthMetrics(health, logger, config);
+
+ const logMessage = JSON.parse(
+ ((logger as jest.Mocked).error.mock.calls[0][0] as string).replace(
+ 'Latest Monitored Stats: ',
+ ''
+ )
+ );
+ expect(logMessage).toMatchObject(health);
+ });
+
+ it('should log as warn if drift exceeds the threshold', () => {
+ const logger = loggingSystemMock.create().get();
+ const config = getTaskManagerConfig({
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
+ });
+ const health = getMockMonitoredHealth({
+ stats: {
+ runtime: {
+ value: {
+ drift: {
+ p99: 60000,
+ },
+ },
+ },
+ },
+ });
+
+ logHealthMetrics(health, logger, config);
+
+ expect((logger as jest.Mocked).warn.mock.calls[0][0] as string).toBe(
+ `Detected delay task start of 60s (which exceeds configured value of 60s)`
+ );
+
+ const secondMessage = JSON.parse(
+ ((logger as jest.Mocked).warn.mock.calls[1][0] as string).replace(
+ `Latest Monitored Stats: `,
+ ''
+ )
+ );
+ expect(secondMessage).toMatchObject(health);
+ });
+
+ it('should log as debug if there are no stats', () => {
+ const logger = loggingSystemMock.create().get();
+ const config = getTaskManagerConfig({
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
+ });
+ const health = {
+ id: '1',
+ status: HealthStatus.OK,
+ timestamp: new Date().toISOString(),
+ last_update: new Date().toISOString(),
+ stats: {},
+ };
+
+ logHealthMetrics(health, logger, config);
+
+ const firstDebug = JSON.parse(
+ (logger as jest.Mocked).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
+ );
+ expect(firstDebug).toMatchObject(health);
+ });
+
+ it('should ignore capacity estimation status', () => {
+ const logger = loggingSystemMock.create().get();
+ const config = getTaskManagerConfig({
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
+ });
+ const health = getMockMonitoredHealth({
+ stats: {
+ capacity_estimation: {
+ status: HealthStatus.Warning,
+ },
+ },
+ });
+
+ logHealthMetrics(health, logger, config);
+
+ const { calculateHealthStatus } = jest.requireMock('./calculate_health_status');
+ expect(calculateHealthStatus).toBeCalledTimes(1);
+ expect(calculateHealthStatus.mock.calls[0][0].stats.capacity_estimation).toBeUndefined();
+ });
+});
+
+function getMockMonitoredHealth(overrides = {}): MonitoredHealth {
+ const stub: MonitoredHealth = {
+ id: '1',
+ status: HealthStatus.OK,
+ timestamp: new Date().toISOString(),
+ last_update: new Date().toISOString(),
+ stats: {
+ configuration: {
+ timestamp: new Date().toISOString(),
+ status: HealthStatus.OK,
+ value: {
+ max_workers: 10,
+ poll_interval: 3000,
+ max_poll_inactivity_cycles: 10,
+ request_capacity: 1000,
+ monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_running_average_window: 50,
+ monitored_task_execution_thresholds: {
+ default: {
+ error_threshold: 90,
+ warn_threshold: 80,
+ },
+ custom: {},
+ },
+ },
+ },
+ workload: {
+ timestamp: new Date().toISOString(),
+ status: HealthStatus.OK,
+ value: {
+ count: 4,
+ task_types: {
+ actions_telemetry: { count: 2, status: { idle: 2 } },
+ alerting_telemetry: { count: 1, status: { idle: 1 } },
+ session_cleanup: { count: 1, status: { idle: 1 } },
+ },
+ schedule: [],
+ overdue: 0,
+ overdue_non_recurring: 0,
+ estimatedScheduleDensity: [],
+ non_recurring: 20,
+ owner_ids: 2,
+ estimated_schedule_density: [],
+ capacity_requirments: {
+ per_minute: 150,
+ per_hour: 360,
+ per_day: 820,
+ },
+ },
+ },
+ runtime: {
+ timestamp: new Date().toISOString(),
+ status: HealthStatus.OK,
+ value: {
+ drift: {
+ p50: 1000,
+ p90: 2000,
+ p95: 2500,
+ p99: 3000,
+ },
+ drift_by_type: {},
+ load: {
+ p50: 1000,
+ p90: 2000,
+ p95: 2500,
+ p99: 3000,
+ },
+ execution: {
+ duration: {},
+ duration_by_persistence: {},
+ persistence: {
+ [TaskPersistence.Recurring]: 10,
+ [TaskPersistence.NonRecurring]: 10,
+ [TaskPersistence.Ephemeral]: 10,
+ },
+ result_frequency_percent_as_number: {},
+ },
+ polling: {
+ last_successful_poll: new Date().toISOString(),
+ duration: [500, 400, 3000],
+ claim_conflicts: [0, 100, 75],
+ claim_mismatches: [0, 100, 75],
+ result_frequency_percent_as_number: [
+ 'NoTasksClaimed',
+ 'NoTasksClaimed',
+ 'NoTasksClaimed',
+ ],
+ },
+ },
+ },
+ },
+ };
+ return (merge(stub, overrides) as unknown) as MonitoredHealth;
+}
+
+function getTaskManagerConfig(overrides: Partial = {}) {
+ return configSchema.validate(
+ overrides.monitored_stats_required_freshness
+ ? {
+ // use `monitored_stats_required_freshness` as poll interval otherwise we might
+ // fail validation as it must be greather than the poll interval
+ poll_interval: overrides.monitored_stats_required_freshness,
+ ...overrides,
+ }
+ : overrides
+ );
+}
diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
new file mode 100644
index 0000000000000..1c98b3272a82d
--- /dev/null
+++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { isEmpty } from 'lodash';
+import { Logger } from '../../../../../src/core/server';
+import { HealthStatus } from '../monitoring';
+import { TaskManagerConfig } from '../config';
+import { MonitoredHealth } from '../routes/health';
+import { calculateHealthStatus } from './calculate_health_status';
+
+export function logHealthMetrics(
+ monitoredHealth: MonitoredHealth,
+ logger: Logger,
+ config: TaskManagerConfig
+) {
+ const healthWithoutCapacity: MonitoredHealth = {
+ ...monitoredHealth,
+ stats: {
+ ...monitoredHealth.stats,
+ capacity_estimation: undefined,
+ },
+ };
+ const statusWithoutCapacity = calculateHealthStatus(healthWithoutCapacity, config);
+ let logAsWarn = statusWithoutCapacity === HealthStatus.Warning;
+ const logAsError =
+ statusWithoutCapacity === HealthStatus.Error && !isEmpty(monitoredHealth.stats);
+ const driftInSeconds = (monitoredHealth.stats.runtime?.value.drift.p99 ?? 0) / 1000;
+
+ if (driftInSeconds >= config.monitored_stats_warn_delayed_task_start_in_seconds) {
+ logger.warn(
+ `Detected delay task start of ${driftInSeconds}s (which exceeds configured value of ${config.monitored_stats_warn_delayed_task_start_in_seconds}s)`
+ );
+ logAsWarn = true;
+ }
+
+ if (logAsError) {
+ logger.error(`Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`);
+ } else if (logAsWarn) {
+ logger.warn(`Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`);
+ } else {
+ logger.debug(`Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`);
+ }
+}
diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
index b8f047836b750..39a7658fb09e4 100644
--- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
@@ -23,6 +23,7 @@ describe('Configuration Statistics Aggregator', () => {
max_poll_inactivity_cycles: 10,
request_capacity: 1000,
monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
monitored_stats_running_average_window: 50,
monitored_task_execution_thresholds: {
default: {
diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
index fdf60fe6dda2c..01bd86ec96db6 100644
--- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
@@ -27,6 +27,7 @@ describe('createMonitoringStatsStream', () => {
max_poll_inactivity_cycles: 10,
request_capacity: 1000,
monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
monitored_stats_running_average_window: 50,
monitored_task_execution_thresholds: {
default: {
diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
index 78511f5a94ca0..0d3b6ebf56de6 100644
--- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts
@@ -51,7 +51,6 @@ interface MonitoredStat {
timestamp: string;
value: T;
}
-
export type RawMonitoredStat = MonitoredStat & {
status: HealthStatus;
};
diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts
index 45db18a3e8385..6c7f722d4c525 100644
--- a/x-pack/plugins/task_manager/server/plugin.test.ts
+++ b/x-pack/plugins/task_manager/server/plugin.test.ts
@@ -25,6 +25,7 @@ describe('TaskManagerPlugin', () => {
max_poll_inactivity_cycles: 10,
request_capacity: 1000,
monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
monitored_stats_required_freshness: 5000,
monitored_stats_running_average_window: 50,
monitored_task_execution_thresholds: {
@@ -55,6 +56,7 @@ describe('TaskManagerPlugin', () => {
max_poll_inactivity_cycles: 10,
request_capacity: 1000,
monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
monitored_stats_required_freshness: 5000,
monitored_stats_running_average_window: 50,
monitored_task_execution_thresholds: {
diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
index f733bb6bfdf2a..66c6805e9160e 100644
--- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
+++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
@@ -45,6 +45,7 @@ describe('TaskPollingLifecycle', () => {
max_poll_inactivity_cycles: 10,
request_capacity: 1000,
monitored_aggregated_stats_refresh_rate: 5000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 60,
monitored_stats_required_freshness: 5000,
monitored_stats_running_average_window: 50,
monitored_task_execution_thresholds: {
diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts
index ae883585e7085..c14eb7e10b726 100644
--- a/x-pack/plugins/task_manager/server/routes/health.test.ts
+++ b/x-pack/plugins/task_manager/server/routes/health.test.ts
@@ -14,10 +14,19 @@ import { healthRoute } from './health';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { sleep } from '../test_utils';
import { loggingSystemMock } from '../../../../../src/core/server/mocks';
-import { Logger } from '../../../../../src/core/server';
-import { MonitoringStats, RawMonitoringStats, summarizeMonitoringStats } from '../monitoring';
+import {
+ HealthStatus,
+ MonitoringStats,
+ RawMonitoringStats,
+ summarizeMonitoringStats,
+} from '../monitoring';
import { ServiceStatusLevels } from 'src/core/server';
import { configSchema, TaskManagerConfig } from '../config';
+import { calculateHealthStatusMock } from '../lib/calculate_health_status.mock';
+
+jest.mock('../lib/log_health_metrics', () => ({
+ logHealthMetrics: jest.fn(),
+}));
describe('healthRoute', () => {
beforeEach(() => {
@@ -38,6 +47,9 @@ describe('healthRoute', () => {
it('logs the Task Manager stats at a fixed interval', async () => {
const router = httpServiceMock.createRouter();
const logger = loggingSystemMock.create().get();
+ const calculateHealthStatus = calculateHealthStatusMock.create();
+ calculateHealthStatus.mockImplementation(() => HealthStatus.OK);
+ const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
const mockStat = mockHealthStats();
await sleep(10);
@@ -55,6 +67,7 @@ describe('healthRoute', () => {
id,
getTaskManagerConfig({
monitored_stats_required_freshness: 1000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 100,
monitored_aggregated_stats_refresh_rate: 60000,
})
);
@@ -65,35 +78,137 @@ describe('healthRoute', () => {
await sleep(600);
stats$.next(nextMockStat);
- const firstDebug = JSON.parse(
- (logger as jest.Mocked).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
- );
- expect(firstDebug).toMatchObject({
+ expect(logHealthMetrics).toBeCalledTimes(2);
+ expect(logHealthMetrics.mock.calls[0][0]).toMatchObject({
id,
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(summarizeMonitoringStats(mockStat, getTaskManagerConfig({}))),
});
+ expect(logHealthMetrics.mock.calls[1][0]).toMatchObject({
+ id,
+ timestamp: expect.any(String),
+ status: expect.any(String),
+ ...ignoreCapacityEstimation(summarizeMonitoringStats(nextMockStat, getTaskManagerConfig({}))),
+ });
+ });
- const secondDebug = JSON.parse(
- (logger as jest.Mocked).debug.mock.calls[1][0].replace('Latest Monitored Stats: ', '')
+ it(`logs at a warn level if the status is warning`, async () => {
+ const router = httpServiceMock.createRouter();
+ const logger = loggingSystemMock.create().get();
+ const calculateHealthStatus = calculateHealthStatusMock.create();
+ calculateHealthStatus.mockImplementation(() => HealthStatus.Warning);
+ const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
+
+ const warnRuntimeStat = mockHealthStats();
+ const warnConfigurationStat = mockHealthStats();
+ const warnWorkloadStat = mockHealthStats();
+
+ const stats$ = new Subject();
+
+ const id = uuid.v4();
+ healthRoute(
+ router,
+ stats$,
+ logger,
+ id,
+ getTaskManagerConfig({
+ monitored_stats_required_freshness: 1000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 120,
+ monitored_aggregated_stats_refresh_rate: 60000,
+ })
);
- expect(secondDebug).not.toMatchObject({
+
+ stats$.next(warnRuntimeStat);
+ await sleep(1001);
+ stats$.next(warnConfigurationStat);
+ await sleep(1001);
+ stats$.next(warnWorkloadStat);
+
+ expect(logHealthMetrics).toBeCalledTimes(3);
+ expect(logHealthMetrics.mock.calls[0][0]).toMatchObject({
id,
timestamp: expect.any(String),
status: expect.any(String),
...ignoreCapacityEstimation(
- summarizeMonitoringStats(skippedMockStat, getTaskManagerConfig({}))
+ summarizeMonitoringStats(warnRuntimeStat, getTaskManagerConfig({}))
),
});
- expect(secondDebug).toMatchObject({
+ expect(logHealthMetrics.mock.calls[1][0]).toMatchObject({
id,
timestamp: expect.any(String),
status: expect.any(String),
- ...ignoreCapacityEstimation(summarizeMonitoringStats(nextMockStat, getTaskManagerConfig({}))),
+ ...ignoreCapacityEstimation(
+ summarizeMonitoringStats(warnConfigurationStat, getTaskManagerConfig({}))
+ ),
});
+ expect(logHealthMetrics.mock.calls[2][0]).toMatchObject({
+ id,
+ timestamp: expect.any(String),
+ status: expect.any(String),
+ ...ignoreCapacityEstimation(
+ summarizeMonitoringStats(warnWorkloadStat, getTaskManagerConfig({}))
+ ),
+ });
+ });
- expect(logger.debug).toHaveBeenCalledTimes(2);
+ it(`logs at an error level if the status is error`, async () => {
+ const router = httpServiceMock.createRouter();
+ const logger = loggingSystemMock.create().get();
+ const calculateHealthStatus = calculateHealthStatusMock.create();
+ calculateHealthStatus.mockImplementation(() => HealthStatus.Error);
+ const { logHealthMetrics } = jest.requireMock('../lib/log_health_metrics');
+
+ const errorRuntimeStat = mockHealthStats();
+ const errorConfigurationStat = mockHealthStats();
+ const errorWorkloadStat = mockHealthStats();
+
+ const stats$ = new Subject();
+
+ const id = uuid.v4();
+ healthRoute(
+ router,
+ stats$,
+ logger,
+ id,
+ getTaskManagerConfig({
+ monitored_stats_required_freshness: 1000,
+ monitored_stats_warn_delayed_task_start_in_seconds: 120,
+ monitored_aggregated_stats_refresh_rate: 60000,
+ })
+ );
+
+ stats$.next(errorRuntimeStat);
+ await sleep(1001);
+ stats$.next(errorConfigurationStat);
+ await sleep(1001);
+ stats$.next(errorWorkloadStat);
+
+ expect(logHealthMetrics).toBeCalledTimes(3);
+ expect(logHealthMetrics.mock.calls[0][0]).toMatchObject({
+ id,
+ timestamp: expect.any(String),
+ status: expect.any(String),
+ ...ignoreCapacityEstimation(
+ summarizeMonitoringStats(errorRuntimeStat, getTaskManagerConfig({}))
+ ),
+ });
+ expect(logHealthMetrics.mock.calls[1][0]).toMatchObject({
+ id,
+ timestamp: expect.any(String),
+ status: expect.any(String),
+ ...ignoreCapacityEstimation(
+ summarizeMonitoringStats(errorConfigurationStat, getTaskManagerConfig({}))
+ ),
+ });
+ expect(logHealthMetrics.mock.calls[2][0]).toMatchObject({
+ id,
+ timestamp: expect.any(String),
+ status: expect.any(String),
+ ...ignoreCapacityEstimation(
+ summarizeMonitoringStats(errorWorkloadStat, getTaskManagerConfig({}))
+ ),
+ });
});
it('returns a error status if the overall stats have not been updated within the required hot freshness', async () => {
diff --git a/x-pack/plugins/task_manager/server/routes/health.ts b/x-pack/plugins/task_manager/server/routes/health.ts
index 0f43575d84481..b5d8a23ba5557 100644
--- a/x-pack/plugins/task_manager/server/routes/health.ts
+++ b/x-pack/plugins/task_manager/server/routes/health.ts
@@ -15,8 +15,6 @@ import {
import { Observable, Subject } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { throttleTime } from 'rxjs/operators';
-import { isString } from 'lodash';
-import { JsonValue } from '@kbn/common-utils';
import { Logger, ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server';
import {
MonitoringStats,
@@ -25,8 +23,14 @@ import {
RawMonitoringStats,
} from '../monitoring';
import { TaskManagerConfig } from '../config';
+import { logHealthMetrics } from '../lib/log_health_metrics';
+import { calculateHealthStatus } from '../lib/calculate_health_status';
-type MonitoredHealth = RawMonitoringStats & { id: string; status: HealthStatus; timestamp: string };
+export type MonitoredHealth = RawMonitoringStats & {
+ id: string;
+ status: HealthStatus;
+ timestamp: string;
+};
const LEVEL_SUMMARY = {
[ServiceStatusLevels.available.toString()]: 'Task Manager is healthy',
@@ -54,26 +58,12 @@ export function healthRoute(
// consider the system unhealthy
const requiredHotStatsFreshness: number = config.monitored_stats_required_freshness;
- // if "cold" health stats are any more stale than the configured refresh (+ a buffer), consider the system unhealthy
- const requiredColdStatsFreshness: number = config.monitored_aggregated_stats_refresh_rate * 1.5;
-
- function calculateStatus(monitoredStats: MonitoringStats): MonitoredHealth {
+ function getHealthStatus(monitoredStats: MonitoringStats) {
+ const summarizedStats = summarizeMonitoringStats(monitoredStats, config);
+ const status = calculateHealthStatus(summarizedStats, config);
const now = Date.now();
const timestamp = new Date(now).toISOString();
- const summarizedStats = summarizeMonitoringStats(monitoredStats, config);
-
- /**
- * If the monitored stats aren't fresh, return a red status
- */
- const healthStatus =
- hasStatus(summarizedStats.stats, HealthStatus.Error) ||
- hasExpiredHotTimestamps(summarizedStats, now, requiredHotStatsFreshness) ||
- hasExpiredColdTimestamps(summarizedStats, now, requiredColdStatsFreshness)
- ? HealthStatus.Error
- : hasStatus(summarizedStats.stats, HealthStatus.Warning)
- ? HealthStatus.Warning
- : HealthStatus.OK;
- return { id: taskManagerId, timestamp, status: healthStatus, ...summarizedStats };
+ return { id: taskManagerId, timestamp, status, ...summarizedStats };
}
const serviceStatus$: Subject = new Subject();
@@ -90,11 +80,11 @@ export function healthRoute(
}),
// Only calculate the summerized stats (calculates all runnign averages and evaluates state)
// when needed by throttling down to the requiredHotStatsFreshness
- map((stats) => withServiceStatus(calculateStatus(stats)))
+ map((stats) => withServiceStatus(getHealthStatus(stats)))
)
.subscribe(([monitoredHealth, serviceStatus]) => {
serviceStatus$.next(serviceStatus);
- logger.debug(`Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`);
+ logHealthMetrics(monitoredHealth, logger, config);
});
router.get(
@@ -109,7 +99,7 @@ export function healthRoute(
): Promise {
return res.ok({
body: lastMonitoredStats
- ? calculateStatus(lastMonitoredStats)
+ ? getHealthStatus(lastMonitoredStats)
: { id: taskManagerId, timestamp: new Date().toISOString(), status: HealthStatus.Error },
});
}
@@ -134,45 +124,3 @@ export function withServiceStatus(
},
];
}
-
-/**
- * If certain "hot" stats are not fresh, then the _health api will should return a Red status
- * @param monitoringStats The monitored stats
- * @param now The time to compare against
- * @param requiredFreshness How fresh should these stats be
- */
-function hasExpiredHotTimestamps(
- monitoringStats: RawMonitoringStats,
- now: number,
- requiredFreshness: number
-): boolean {
- return (
- now -
- getOldestTimestamp(
- monitoringStats.last_update,
- monitoringStats.stats.runtime?.value.polling.last_successful_poll
- ) >
- requiredFreshness
- );
-}
-
-function hasExpiredColdTimestamps(
- monitoringStats: RawMonitoringStats,
- now: number,
- requiredFreshness: number
-): boolean {
- return now - getOldestTimestamp(monitoringStats.stats.workload?.timestamp) > requiredFreshness;
-}
-
-function hasStatus(stats: RawMonitoringStats['stats'], status: HealthStatus): boolean {
- return Object.values(stats)
- .map((stat) => stat?.status === status)
- .includes(true);
-}
-
-function getOldestTimestamp(...timestamps: Array): number {
- const validTimestamps = timestamps
- .map((timestamp) => (isString(timestamp) ? Date.parse(timestamp) : NaN))
- .filter((timestamp) => !isNaN(timestamp));
- return validTimestamps.length ? Math.min(...validTimestamps) : 0;
-}
From 1cf82cbc3689c2e0f5694fea777b18c8364e7696 Mon Sep 17 00:00:00 2001
From: Dominique Clarke
Date: Wed, 16 Jun 2021 15:29:28 -0400
Subject: [PATCH 44/98] [Uptime] refactor Synthetics Integration package UI
(#102080)
* refactor contexts
* add http, tcp, and icmp folders
* adjust types
* adjust useUpdatePolicy hook
* adjust synthetics policy create and edit wrappers
* adjust validation
* fix typo and types
* remove typo
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../fleet_package/contexts/http_context.tsx | 60 ++++
.../fleet_package/contexts/http_provider.tsx | 69 ++++
.../fleet_package/contexts/icmp_context.tsx | 61 ++++
.../fleet_package/contexts/index.ts | 30 +-
.../contexts/monitor_type_context.tsx | 47 +++
.../contexts/simple_fields_context.tsx | 60 ----
.../fleet_package/contexts/tcp_context.tsx | 60 ++++
.../fleet_package/contexts/tcp_provider.tsx | 62 ++++
.../fleet_package/custom_fields.test.tsx | 69 ++--
.../fleet_package/custom_fields.tsx | 324 ++----------------
.../advanced_fields.test.tsx} | 10 +-
.../advanced_fields.tsx} | 14 +-
.../fleet_package/http/simple_fields.tsx | 200 +++++++++++
.../fleet_package/icmp/simple_fields.tsx | 204 +++++++++++
.../synthetics_policy_create_extension.tsx | 91 +++--
...s_policy_create_extension_wrapper.test.tsx | 74 ++--
...hetics_policy_create_extension_wrapper.tsx | 23 +-
.../synthetics_policy_edit_extension.tsx | 85 +++--
...ics_policy_edit_extension_wrapper.test.tsx | 106 +++---
...nthetics_policy_edit_extension_wrapper.tsx | 135 ++++----
.../advanced_fields.test.tsx} | 8 +-
.../advanced_fields.tsx} | 6 +-
.../fleet_package/tcp/simple_fields.tsx | 171 +++++++++
.../public/components/fleet_package/types.tsx | 40 ++-
.../fleet_package/use_update_policy.test.tsx | 99 ++----
.../fleet_package/use_update_policy.ts | 46 ++-
.../components/fleet_package/validation.tsx | 7 +-
27 files changed, 1404 insertions(+), 757 deletions(-)
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/contexts/http_context.tsx
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/contexts/http_provider.tsx
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/contexts/icmp_context.tsx
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/contexts/monitor_type_context.tsx
delete mode 100644 x-pack/plugins/uptime/public/components/fleet_package/contexts/simple_fields_context.tsx
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_context.tsx
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_provider.tsx
rename x-pack/plugins/uptime/public/components/fleet_package/{http_advanced_fields.test.tsx => http/advanced_fields.test.tsx} (95%)
rename x-pack/plugins/uptime/public/components/fleet_package/{http_advanced_fields.tsx => http/advanced_fields.tsx} (97%)
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/http/simple_fields.tsx
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx
rename x-pack/plugins/uptime/public/components/fleet_package/{tcp_advanced_fields.test.tsx => tcp/advanced_fields.test.tsx} (92%)
rename x-pack/plugins/uptime/public/components/fleet_package/{tcp_advanced_fields.tsx => tcp/advanced_fields.tsx} (97%)
create mode 100644 x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/http_context.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/http_context.tsx
new file mode 100644
index 0000000000000..d1306836afa9c
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/http_context.tsx
@@ -0,0 +1,60 @@
+/*
+ * Copyright 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, { createContext, useContext, useMemo, useState } from 'react';
+import { IHTTPSimpleFields, ConfigKeys, ScheduleUnit, DataStream } from '../types';
+
+interface IHTTPSimpleFieldsContext {
+ setFields: React.Dispatch>;
+ fields: IHTTPSimpleFields;
+ defaultValues: IHTTPSimpleFields;
+}
+
+interface IHTTPSimpleFieldsContextProvider {
+ children: React.ReactNode;
+ defaultValues?: IHTTPSimpleFields;
+}
+
+export const initialValues = {
+ [ConfigKeys.URLS]: '',
+ [ConfigKeys.MAX_REDIRECTS]: '0',
+ [ConfigKeys.MONITOR_TYPE]: DataStream.HTTP,
+ [ConfigKeys.SCHEDULE]: {
+ number: '3',
+ unit: ScheduleUnit.MINUTES,
+ },
+ [ConfigKeys.APM_SERVICE_NAME]: '',
+ [ConfigKeys.TAGS]: [],
+ [ConfigKeys.TIMEOUT]: '16',
+};
+
+const defaultContext: IHTTPSimpleFieldsContext = {
+ setFields: (_fields: React.SetStateAction) => {
+ throw new Error(
+ 'setFields was not initialized for HTTP Simple Fields, set it when you invoke the context'
+ );
+ },
+ fields: initialValues, // mutable
+ defaultValues: initialValues, // immutable
+};
+
+export const HTTPSimpleFieldsContext = createContext(defaultContext);
+
+export const HTTPSimpleFieldsContextProvider = ({
+ children,
+ defaultValues = initialValues,
+}: IHTTPSimpleFieldsContextProvider) => {
+ const [fields, setFields] = useState(defaultValues);
+
+ const value = useMemo(() => {
+ return { fields, setFields, defaultValues };
+ }, [fields, defaultValues]);
+
+ return ;
+};
+
+export const useHTTPSimpleFieldsContext = () => useContext(HTTPSimpleFieldsContext);
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/http_provider.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/http_provider.tsx
new file mode 100644
index 0000000000000..e48de76862e24
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/http_provider.tsx
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { ReactNode } from 'react';
+import { IHTTPSimpleFields, IHTTPAdvancedFields, ITLSFields, ConfigKeys } from '../types';
+import {
+ HTTPSimpleFieldsContextProvider,
+ HTTPAdvancedFieldsContextProvider,
+ TLSFieldsContextProvider,
+} from '.';
+
+interface HTTPContextProviderProps {
+ defaultValues?: any;
+ children: ReactNode;
+}
+
+export const HTTPContextProvider = ({ defaultValues, children }: HTTPContextProviderProps) => {
+ const httpAdvancedFields: IHTTPAdvancedFields | undefined = defaultValues
+ ? {
+ [ConfigKeys.USERNAME]: defaultValues[ConfigKeys.USERNAME],
+ [ConfigKeys.PASSWORD]: defaultValues[ConfigKeys.PASSWORD],
+ [ConfigKeys.PROXY_URL]: defaultValues[ConfigKeys.PROXY_URL],
+ [ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE]:
+ defaultValues[ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE],
+ [ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE]:
+ defaultValues[ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE],
+ [ConfigKeys.RESPONSE_BODY_INDEX]: defaultValues[ConfigKeys.RESPONSE_BODY_INDEX],
+ [ConfigKeys.RESPONSE_HEADERS_CHECK]: defaultValues[ConfigKeys.RESPONSE_HEADERS_CHECK],
+ [ConfigKeys.RESPONSE_HEADERS_INDEX]: defaultValues[ConfigKeys.RESPONSE_HEADERS_INDEX],
+ [ConfigKeys.RESPONSE_STATUS_CHECK]: defaultValues[ConfigKeys.RESPONSE_STATUS_CHECK],
+ [ConfigKeys.REQUEST_BODY_CHECK]: defaultValues[ConfigKeys.REQUEST_BODY_CHECK],
+ [ConfigKeys.REQUEST_HEADERS_CHECK]: defaultValues[ConfigKeys.REQUEST_HEADERS_CHECK],
+ [ConfigKeys.REQUEST_METHOD_CHECK]: defaultValues[ConfigKeys.REQUEST_METHOD_CHECK],
+ }
+ : undefined;
+ const httpSimpleFields: IHTTPSimpleFields | undefined = defaultValues
+ ? {
+ [ConfigKeys.APM_SERVICE_NAME]: defaultValues[ConfigKeys.APM_SERVICE_NAME],
+ [ConfigKeys.MAX_REDIRECTS]: defaultValues[ConfigKeys.MAX_REDIRECTS],
+ [ConfigKeys.MONITOR_TYPE]: defaultValues[ConfigKeys.MONITOR_TYPE],
+ [ConfigKeys.SCHEDULE]: defaultValues[ConfigKeys.SCHEDULE],
+ [ConfigKeys.TAGS]: defaultValues[ConfigKeys.TAGS],
+ [ConfigKeys.TIMEOUT]: defaultValues[ConfigKeys.TIMEOUT],
+ [ConfigKeys.URLS]: defaultValues[ConfigKeys.URLS],
+ }
+ : undefined;
+ const tlsFields: ITLSFields | undefined = defaultValues
+ ? {
+ [ConfigKeys.TLS_CERTIFICATE_AUTHORITIES]:
+ defaultValues[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES],
+ [ConfigKeys.TLS_CERTIFICATE]: defaultValues[ConfigKeys.TLS_CERTIFICATE],
+ [ConfigKeys.TLS_KEY]: defaultValues[ConfigKeys.TLS_KEY],
+ [ConfigKeys.TLS_KEY_PASSPHRASE]: defaultValues[ConfigKeys.TLS_KEY_PASSPHRASE],
+ [ConfigKeys.TLS_VERIFICATION_MODE]: defaultValues[ConfigKeys.TLS_VERIFICATION_MODE],
+ [ConfigKeys.TLS_VERSION]: defaultValues[ConfigKeys.TLS_VERSION],
+ }
+ : undefined;
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/icmp_context.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/icmp_context.tsx
new file mode 100644
index 0000000000000..93c67c6133ce9
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/icmp_context.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { createContext, useContext, useMemo, useState } from 'react';
+import { IICMPSimpleFields, ConfigKeys, ScheduleUnit, DataStream } from '../types';
+
+interface IICMPSimpleFieldsContext {
+ setFields: React.Dispatch>;
+ fields: IICMPSimpleFields;
+ defaultValues: IICMPSimpleFields;
+}
+
+interface IICMPSimpleFieldsContextProvider {
+ children: React.ReactNode;
+ defaultValues?: IICMPSimpleFields;
+}
+
+export const initialValues = {
+ [ConfigKeys.HOSTS]: '',
+ [ConfigKeys.MAX_REDIRECTS]: '0',
+ [ConfigKeys.MONITOR_TYPE]: DataStream.ICMP,
+ [ConfigKeys.SCHEDULE]: {
+ number: '3',
+ unit: ScheduleUnit.MINUTES,
+ },
+ [ConfigKeys.APM_SERVICE_NAME]: '',
+ [ConfigKeys.TAGS]: [],
+ [ConfigKeys.TIMEOUT]: '16',
+ [ConfigKeys.WAIT]: '1',
+};
+
+const defaultContext: IICMPSimpleFieldsContext = {
+ setFields: (_fields: React.SetStateAction) => {
+ throw new Error(
+ 'setFields was not initialized for ICMP Simple Fields, set it when you invoke the context'
+ );
+ },
+ fields: initialValues, // mutable
+ defaultValues: initialValues, // immutable
+};
+
+export const ICMPSimpleFieldsContext = createContext(defaultContext);
+
+export const ICMPSimpleFieldsContextProvider = ({
+ children,
+ defaultValues = initialValues,
+}: IICMPSimpleFieldsContextProvider) => {
+ const [fields, setFields] = useState(defaultValues);
+
+ const value = useMemo(() => {
+ return { fields, setFields, defaultValues };
+ }, [fields, defaultValues]);
+
+ return ;
+};
+
+export const useICMPSimpleFieldsContext = () => useContext(ICMPSimpleFieldsContext);
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts b/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts
index bea3e9d5641a5..f84a4e75df922 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts
+++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/index.ts
@@ -6,11 +6,29 @@
*/
export {
- SimpleFieldsContext,
- SimpleFieldsContextProvider,
- initialValues as defaultSimpleFields,
- useSimpleFieldsContext,
-} from './simple_fields_context';
+ MonitorTypeContext,
+ MonitorTypeContextProvider,
+ initialValue as defaultMonitorType,
+ useMonitorTypeContext,
+} from './monitor_type_context';
+export {
+ HTTPSimpleFieldsContext,
+ HTTPSimpleFieldsContextProvider,
+ initialValues as defaultHTTPSimpleFields,
+ useHTTPSimpleFieldsContext,
+} from './http_context';
+export {
+ TCPSimpleFieldsContext,
+ TCPSimpleFieldsContextProvider,
+ initialValues as defaultTCPSimpleFields,
+ useTCPSimpleFieldsContext,
+} from './tcp_context';
+export {
+ ICMPSimpleFieldsContext,
+ ICMPSimpleFieldsContextProvider,
+ initialValues as defaultICMPSimpleFields,
+ useICMPSimpleFieldsContext,
+} from './icmp_context';
export {
TCPAdvancedFieldsContext,
TCPAdvancedFieldsContextProvider,
@@ -29,3 +47,5 @@ export {
initialValues as defaultTLSFields,
useTLSFieldsContext,
} from './tls_fields_context';
+export { HTTPContextProvider } from './http_provider';
+export { TCPContextProvider } from './tcp_provider';
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/monitor_type_context.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/monitor_type_context.tsx
new file mode 100644
index 0000000000000..6e9a5de83c2fe
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/monitor_type_context.tsx
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { createContext, useContext, useMemo, useState } from 'react';
+import { DataStream } from '../types';
+
+interface IMonitorTypeFieldsContext {
+ setMonitorType: React.Dispatch>;
+ monitorType: DataStream;
+ defaultValue: DataStream;
+}
+
+interface IMonitorTypeFieldsContextProvider {
+ children: React.ReactNode;
+ defaultValue?: DataStream;
+}
+
+export const initialValue = DataStream.HTTP;
+
+const defaultContext: IMonitorTypeFieldsContext = {
+ setMonitorType: (_monitorType: React.SetStateAction) => {
+ throw new Error('setMonitorType was not initialized, set it when you invoke the context');
+ },
+ monitorType: initialValue, // mutable
+ defaultValue: initialValue, // immutable
+};
+
+export const MonitorTypeContext = createContext(defaultContext);
+
+export const MonitorTypeContextProvider = ({
+ children,
+ defaultValue = initialValue,
+}: IMonitorTypeFieldsContextProvider) => {
+ const [monitorType, setMonitorType] = useState(defaultValue);
+
+ const value = useMemo(() => {
+ return { monitorType, setMonitorType, defaultValue };
+ }, [monitorType, defaultValue]);
+
+ return ;
+};
+
+export const useMonitorTypeContext = () => useContext(MonitorTypeContext);
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/simple_fields_context.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/simple_fields_context.tsx
deleted file mode 100644
index 1d981ed4c2c8f..0000000000000
--- a/x-pack/plugins/uptime/public/components/fleet_package/contexts/simple_fields_context.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { createContext, useContext, useMemo, useState } from 'react';
-import { ISimpleFields, ConfigKeys, ScheduleUnit, DataStream } from '../types';
-
-interface ISimpleFieldsContext {
- setFields: React.Dispatch>;
- fields: ISimpleFields;
- defaultValues: ISimpleFields;
-}
-
-interface ISimpleFieldsContextProvider {
- children: React.ReactNode;
- defaultValues?: ISimpleFields;
-}
-
-export const initialValues = {
- [ConfigKeys.HOSTS]: '',
- [ConfigKeys.MAX_REDIRECTS]: '0',
- [ConfigKeys.MONITOR_TYPE]: DataStream.HTTP,
- [ConfigKeys.SCHEDULE]: {
- number: '3',
- unit: ScheduleUnit.MINUTES,
- },
- [ConfigKeys.APM_SERVICE_NAME]: '',
- [ConfigKeys.TAGS]: [],
- [ConfigKeys.TIMEOUT]: '16',
- [ConfigKeys.URLS]: '',
- [ConfigKeys.WAIT]: '1',
-};
-
-const defaultContext: ISimpleFieldsContext = {
- setFields: (_fields: React.SetStateAction) => {
- throw new Error('setSimpleFields was not initialized, set it when you invoke the context');
- },
- fields: initialValues, // mutable
- defaultValues: initialValues, // immutable
-};
-
-export const SimpleFieldsContext = createContext(defaultContext);
-
-export const SimpleFieldsContextProvider = ({
- children,
- defaultValues = initialValues,
-}: ISimpleFieldsContextProvider) => {
- const [fields, setFields] = useState(defaultValues);
-
- const value = useMemo(() => {
- return { fields, setFields, defaultValues };
- }, [fields, defaultValues]);
-
- return ;
-};
-
-export const useSimpleFieldsContext = () => useContext(SimpleFieldsContext);
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_context.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_context.tsx
new file mode 100644
index 0000000000000..6020a7ff2bff8
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_context.tsx
@@ -0,0 +1,60 @@
+/*
+ * Copyright 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, { createContext, useContext, useMemo, useState } from 'react';
+import { ITCPSimpleFields, ConfigKeys, ScheduleUnit, DataStream } from '../types';
+
+interface ITCPSimpleFieldsContext {
+ setFields: React.Dispatch>;
+ fields: ITCPSimpleFields;
+ defaultValues: ITCPSimpleFields;
+}
+
+interface ITCPSimpleFieldsContextProvider {
+ children: React.ReactNode;
+ defaultValues?: ITCPSimpleFields;
+}
+
+export const initialValues = {
+ [ConfigKeys.HOSTS]: '',
+ [ConfigKeys.MAX_REDIRECTS]: '0',
+ [ConfigKeys.MONITOR_TYPE]: DataStream.TCP,
+ [ConfigKeys.SCHEDULE]: {
+ number: '3',
+ unit: ScheduleUnit.MINUTES,
+ },
+ [ConfigKeys.APM_SERVICE_NAME]: '',
+ [ConfigKeys.TAGS]: [],
+ [ConfigKeys.TIMEOUT]: '16',
+};
+
+const defaultContext: ITCPSimpleFieldsContext = {
+ setFields: (_fields: React.SetStateAction) => {
+ throw new Error(
+ 'setFields was not initialized for TCP Simple Fields, set it when you invoke the context'
+ );
+ },
+ fields: initialValues, // mutable
+ defaultValues: initialValues, // immutable
+};
+
+export const TCPSimpleFieldsContext = createContext(defaultContext);
+
+export const TCPSimpleFieldsContextProvider = ({
+ children,
+ defaultValues = initialValues,
+}: ITCPSimpleFieldsContextProvider) => {
+ const [fields, setFields] = useState(defaultValues);
+
+ const value = useMemo(() => {
+ return { fields, setFields, defaultValues };
+ }, [fields, defaultValues]);
+
+ return ;
+};
+
+export const useTCPSimpleFieldsContext = () => useContext(TCPSimpleFieldsContext);
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_provider.tsx b/x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_provider.tsx
new file mode 100644
index 0000000000000..666839803f4d6
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/contexts/tcp_provider.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { ReactNode } from 'react';
+import { ConfigKeys, ITCPSimpleFields, ITCPAdvancedFields, ITLSFields } from '../types';
+import {
+ TCPSimpleFieldsContextProvider,
+ TCPAdvancedFieldsContextProvider,
+ TLSFieldsContextProvider,
+} from '.';
+
+interface TCPContextProviderProps {
+ defaultValues?: any;
+ children: ReactNode;
+}
+
+/**
+ * Exports Synthetics-specific package policy instructions
+ * for use in the Ingest app create / edit package policy
+ */
+export const TCPContextProvider = ({ defaultValues, children }: TCPContextProviderProps) => {
+ const tcpSimpleFields: ITCPSimpleFields | undefined = defaultValues
+ ? {
+ [ConfigKeys.APM_SERVICE_NAME]: defaultValues[ConfigKeys.APM_SERVICE_NAME],
+ [ConfigKeys.HOSTS]: defaultValues[ConfigKeys.HOSTS],
+ [ConfigKeys.MONITOR_TYPE]: defaultValues[ConfigKeys.MONITOR_TYPE],
+ [ConfigKeys.SCHEDULE]: defaultValues[ConfigKeys.SCHEDULE],
+ [ConfigKeys.TAGS]: defaultValues[ConfigKeys.TAGS],
+ [ConfigKeys.TIMEOUT]: defaultValues[ConfigKeys.TIMEOUT],
+ }
+ : undefined;
+ const tcpAdvancedFields: ITCPAdvancedFields | undefined = defaultValues
+ ? {
+ [ConfigKeys.PROXY_URL]: defaultValues[ConfigKeys.PROXY_URL],
+ [ConfigKeys.PROXY_USE_LOCAL_RESOLVER]: defaultValues[ConfigKeys.PROXY_USE_LOCAL_RESOLVER],
+ [ConfigKeys.RESPONSE_RECEIVE_CHECK]: defaultValues[ConfigKeys.RESPONSE_RECEIVE_CHECK],
+ [ConfigKeys.REQUEST_SEND_CHECK]: defaultValues[ConfigKeys.REQUEST_SEND_CHECK],
+ }
+ : undefined;
+ const tlsFields: ITLSFields | undefined = defaultValues
+ ? {
+ [ConfigKeys.TLS_CERTIFICATE_AUTHORITIES]:
+ defaultValues[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES],
+ [ConfigKeys.TLS_CERTIFICATE]: defaultValues[ConfigKeys.TLS_CERTIFICATE],
+ [ConfigKeys.TLS_KEY]: defaultValues[ConfigKeys.TLS_KEY],
+ [ConfigKeys.TLS_KEY_PASSPHRASE]: defaultValues[ConfigKeys.TLS_KEY_PASSPHRASE],
+ [ConfigKeys.TLS_VERIFICATION_MODE]: defaultValues[ConfigKeys.TLS_VERIFICATION_MODE],
+ [ConfigKeys.TLS_VERSION]: defaultValues[ConfigKeys.TLS_VERSION],
+ }
+ : undefined;
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx
index b5fec58d4da85..e114ea72b8f49 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx
@@ -9,18 +9,15 @@ import React from 'react';
import { fireEvent, waitFor } from '@testing-library/react';
import { render } from '../../lib/helper/rtl_helpers';
import {
- SimpleFieldsContextProvider,
- HTTPAdvancedFieldsContextProvider,
- TCPAdvancedFieldsContextProvider,
- TLSFieldsContextProvider,
- defaultSimpleFields,
- defaultTLSFields,
- defaultHTTPAdvancedFields,
- defaultTCPAdvancedFields,
+ TCPContextProvider,
+ HTTPContextProvider,
+ ICMPSimpleFieldsContextProvider,
+ MonitorTypeContextProvider,
} from './contexts';
import { CustomFields } from './custom_fields';
import { ConfigKeys, DataStream, ScheduleUnit } from './types';
import { validate as centralValidation } from './validation';
+import { defaultConfig } from './synthetics_policy_create_extension';
// ensures that fields appropriately match to their label
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
@@ -29,25 +26,21 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
const defaultValidation = centralValidation[DataStream.HTTP];
-const defaultConfig = {
- ...defaultSimpleFields,
- ...defaultTLSFields,
- ...defaultHTTPAdvancedFields,
- ...defaultTCPAdvancedFields,
-};
+const defaultHTTPConfig = defaultConfig[DataStream.HTTP];
+const defaultTCPConfig = defaultConfig[DataStream.TCP];
describe(' ', () => {
const WrappedComponent = ({ validate = defaultValidation, typeEditable = false }) => {
return (
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
);
};
@@ -63,20 +56,20 @@ describe(' ', () => {
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
expect(monitorType).not.toBeInTheDocument();
expect(url).toBeInTheDocument();
- expect(url.value).toEqual(defaultConfig[ConfigKeys.URLS]);
+ expect(url.value).toEqual(defaultHTTPConfig[ConfigKeys.URLS]);
expect(proxyUrl).toBeInTheDocument();
- expect(proxyUrl.value).toEqual(defaultConfig[ConfigKeys.PROXY_URL]);
+ expect(proxyUrl.value).toEqual(defaultHTTPConfig[ConfigKeys.PROXY_URL]);
expect(monitorIntervalNumber).toBeInTheDocument();
- expect(monitorIntervalNumber.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].number);
+ expect(monitorIntervalNumber.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].number);
expect(monitorIntervalUnit).toBeInTheDocument();
- expect(monitorIntervalUnit.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].unit);
+ expect(monitorIntervalUnit.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].unit);
// expect(tags).toBeInTheDocument();
expect(apmServiceName).toBeInTheDocument();
- expect(apmServiceName.value).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ expect(apmServiceName.value).toEqual(defaultHTTPConfig[ConfigKeys.APM_SERVICE_NAME]);
expect(maxRedirects).toBeInTheDocument();
- expect(maxRedirects.value).toEqual(`${defaultConfig[ConfigKeys.MAX_REDIRECTS]}`);
+ expect(maxRedirects.value).toEqual(`${defaultHTTPConfig[ConfigKeys.MAX_REDIRECTS]}`);
expect(timeout).toBeInTheDocument();
- expect(timeout.value).toEqual(`${defaultConfig[ConfigKeys.TIMEOUT]}`);
+ expect(timeout.value).toEqual(`${defaultHTTPConfig[ConfigKeys.TIMEOUT]}`);
// ensure other monitor type options are not in the DOM
expect(queryByLabelText('Host')).not.toBeInTheDocument();
@@ -116,11 +109,15 @@ describe(' ', () => {
expect(verificationMode).toBeInTheDocument();
await waitFor(() => {
- expect(ca.value).toEqual(defaultConfig[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES].value);
- expect(clientKey.value).toEqual(defaultConfig[ConfigKeys.TLS_KEY].value);
- expect(clientKeyPassphrase.value).toEqual(defaultConfig[ConfigKeys.TLS_KEY_PASSPHRASE].value);
- expect(clientCertificate.value).toEqual(defaultConfig[ConfigKeys.TLS_CERTIFICATE].value);
- expect(verificationMode.value).toEqual(defaultConfig[ConfigKeys.TLS_VERIFICATION_MODE].value);
+ expect(ca.value).toEqual(defaultHTTPConfig[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES].value);
+ expect(clientKey.value).toEqual(defaultHTTPConfig[ConfigKeys.TLS_KEY].value);
+ expect(clientKeyPassphrase.value).toEqual(
+ defaultHTTPConfig[ConfigKeys.TLS_KEY_PASSPHRASE].value
+ );
+ expect(clientCertificate.value).toEqual(defaultHTTPConfig[ConfigKeys.TLS_CERTIFICATE].value);
+ expect(verificationMode.value).toEqual(
+ defaultHTTPConfig[ConfigKeys.TLS_VERIFICATION_MODE].value
+ );
});
});
@@ -157,14 +154,14 @@ describe(' ', () => {
);
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
expect(monitorType).toBeInTheDocument();
- expect(monitorType.value).toEqual(defaultConfig[ConfigKeys.MONITOR_TYPE]);
+ expect(monitorType.value).toEqual(defaultHTTPConfig[ConfigKeys.MONITOR_TYPE]);
fireEvent.change(monitorType, { target: { value: DataStream.TCP } });
// expect tcp fields to be in the DOM
const host = getByLabelText('Host:Port') as HTMLInputElement;
expect(host).toBeInTheDocument();
- expect(host.value).toEqual(defaultConfig[ConfigKeys.HOSTS]);
+ expect(host.value).toEqual(defaultTCPConfig[ConfigKeys.HOSTS]);
// expect HTTP fields not to be in the DOM
expect(queryByLabelText('URL')).not.toBeInTheDocument();
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx
index e6703a6eaa97c..0d9291261b82d 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx
@@ -5,28 +5,26 @@
* 2.0.
*/
-import React, { useEffect, useState, memo } from 'react';
+import React, { useState, memo } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
- EuiFieldText,
- EuiFieldNumber,
EuiSelect,
EuiSpacer,
EuiDescribedFormGroup,
EuiCheckbox,
} from '@elastic/eui';
-import { ConfigKeys, DataStream, ISimpleFields, Validation } from './types';
-import { useSimpleFieldsContext } from './contexts';
+import { ConfigKeys, DataStream, Validation } from './types';
+import { useMonitorTypeContext } from './contexts';
import { TLSFields, TLSRole } from './tls_fields';
-import { ComboBox } from './combo_box';
-import { OptionalLabel } from './optional_label';
-import { HTTPAdvancedFields } from './http_advanced_fields';
-import { TCPAdvancedFields } from './tcp_advanced_fields';
-import { ScheduleField } from './schedule_field';
+import { HTTPSimpleFields } from './http/simple_fields';
+import { HTTPAdvancedFields } from './http/advanced_fields';
+import { TCPSimpleFields } from './tcp/simple_fields';
+import { TCPAdvancedFields } from './tcp/advanced_fields';
+import { ICMPSimpleFields } from './icmp/simple_fields';
interface Props {
typeEditable?: boolean;
@@ -37,26 +35,22 @@ interface Props {
export const CustomFields = memo(
({ typeEditable = false, isTLSEnabled: defaultIsTLSEnabled = false, validate }) => {
const [isTLSEnabled, setIsTLSEnabled] = useState(defaultIsTLSEnabled);
- const { fields, setFields, defaultValues } = useSimpleFieldsContext();
- const { type } = fields;
+ const { monitorType, setMonitorType } = useMonitorTypeContext();
- const isHTTP = fields[ConfigKeys.MONITOR_TYPE] === DataStream.HTTP;
- const isTCP = fields[ConfigKeys.MONITOR_TYPE] === DataStream.TCP;
- const isICMP = fields[ConfigKeys.MONITOR_TYPE] === DataStream.ICMP;
+ const isHTTP = monitorType === DataStream.HTTP;
+ const isTCP = monitorType === DataStream.TCP;
- // reset monitor type specific fields any time a monitor type is switched
- useEffect(() => {
- if (typeEditable) {
- setFields((prevFields: ISimpleFields) => ({
- ...prevFields,
- [ConfigKeys.HOSTS]: defaultValues[ConfigKeys.HOSTS],
- [ConfigKeys.URLS]: defaultValues[ConfigKeys.URLS],
- }));
+ const renderSimpleFields = (type: DataStream) => {
+ switch (type) {
+ case DataStream.HTTP:
+ return ;
+ case DataStream.ICMP:
+ return ;
+ case DataStream.TCP:
+ return ;
+ default:
+ return null;
}
- }, [defaultValues, type, typeEditable, setFields]);
-
- const handleInputChange = ({ value, configKey }: { value: unknown; configKey: ConfigKeys }) => {
- setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
};
return (
@@ -88,7 +82,7 @@ export const CustomFields = memo(
defaultMessage="Monitor Type"
/>
}
- isInvalid={!!validate[ConfigKeys.MONITOR_TYPE]?.(fields[ConfigKeys.MONITOR_TYPE])}
+ isInvalid={!!validate[ConfigKeys.MONITOR_TYPE]?.(monitorType)}
error={
(
>
- handleInputChange({
- value: event.target.value,
- configKey: ConfigKeys.MONITOR_TYPE,
- })
- }
+ value={monitorType}
+ onChange={(event) => setMonitorType(event.target.value as DataStream)}
data-test-subj="syntheticsMonitorTypeField"
/>
)}
- {isHTTP && (
-
- }
- isInvalid={!!validate[ConfigKeys.URLS]?.(fields[ConfigKeys.URLS])}
- error={
-
- }
- >
-
- handleInputChange({ value: event.target.value, configKey: ConfigKeys.URLS })
- }
- data-test-subj="syntheticsUrlField"
- />
-
- )}
- {isTCP && (
-
- }
- isInvalid={!!validate[ConfigKeys.HOSTS]?.(fields[ConfigKeys.HOSTS])}
- error={
-
- }
- >
-
- handleInputChange({
- value: event.target.value,
- configKey: ConfigKeys.HOSTS,
- })
- }
- data-test-subj="syntheticsTCPHostField"
- />
-
- )}
- {isICMP && (
-
- }
- isInvalid={!!validate[ConfigKeys.HOSTS]?.(fields[ConfigKeys.HOSTS])}
- error={
-
- }
- >
-
- handleInputChange({
- value: event.target.value,
- configKey: ConfigKeys.HOSTS,
- })
- }
- data-test-subj="syntheticsICMPHostField"
- />
-
- )}
-
- }
- isInvalid={!!validate[ConfigKeys.SCHEDULE]?.(fields[ConfigKeys.SCHEDULE])}
- error={
-
- }
- >
-
- handleInputChange({
- value: schedule,
- configKey: ConfigKeys.SCHEDULE,
- })
- }
- number={fields[ConfigKeys.SCHEDULE].number}
- unit={fields[ConfigKeys.SCHEDULE].unit}
- />
-
- {isICMP && (
-
- }
- isInvalid={!!validate[ConfigKeys.WAIT]?.(fields[ConfigKeys.WAIT])}
- error={
-
- }
- labelAppend={ }
- helpText={
-
- }
- >
-
- handleInputChange({ value: event.target.value, configKey: ConfigKeys.WAIT })
- }
- step={'any'}
- />
-
- )}
-
- }
- labelAppend={ }
- helpText={
-
- }
- >
-
- handleInputChange({
- value: event.target.value,
- configKey: ConfigKeys.APM_SERVICE_NAME,
- })
- }
- data-test-subj="syntheticsAPMServiceName"
- />
-
- {isHTTP && (
-
- }
- isInvalid={
- !!validate[ConfigKeys.MAX_REDIRECTS]?.(fields[ConfigKeys.MAX_REDIRECTS])
- }
- error={
-
- }
- labelAppend={ }
- helpText={
-
- }
- >
-
- handleInputChange({
- value: event.target.value,
- configKey: ConfigKeys.MAX_REDIRECTS,
- })
- }
- />
-
- )}
-
- }
- isInvalid={
- !!validate[ConfigKeys.TIMEOUT]?.(
- fields[ConfigKeys.TIMEOUT],
- fields[ConfigKeys.SCHEDULE].number,
- fields[ConfigKeys.SCHEDULE].unit
- )
- }
- error={
-
- }
- helpText={
-
- }
- >
-
- handleInputChange({
- value: event.target.value,
- configKey: ConfigKeys.TIMEOUT,
- })
- }
- step={'any'}
- />
-
-
- }
- labelAppend={ }
- helpText={
-
- }
- >
- handleInputChange({ value, configKey: ConfigKeys.TAGS })}
- data-test-subj="syntheticsTags"
- />
-
+ {renderSimpleFields(monitorType)}
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/http_advanced_fields.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.test.tsx
similarity index 95%
rename from x-pack/plugins/uptime/public/components/fleet_package/http_advanced_fields.test.tsx
rename to x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.test.tsx
index b1a37be1bffb6..69c1d897f7847 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/http_advanced_fields.test.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.test.tsx
@@ -7,14 +7,14 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
-import { render } from '../../lib/helper/rtl_helpers';
-import { HTTPAdvancedFields } from './http_advanced_fields';
-import { ConfigKeys, DataStream, HTTPMethod, IHTTPAdvancedFields, Validation } from './types';
+import { render } from '../../../lib/helper/rtl_helpers';
+import { HTTPAdvancedFields } from './advanced_fields';
+import { ConfigKeys, DataStream, HTTPMethod, IHTTPAdvancedFields, Validation } from '../types';
import {
HTTPAdvancedFieldsContextProvider,
defaultHTTPAdvancedFields as defaultConfig,
-} from './contexts';
-import { validate as centralValidation } from './validation';
+} from '../contexts';
+import { validate as centralValidation } from '../validation';
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
htmlIdGenerator: () => () => `id-${Math.random()}`,
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/http_advanced_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.tsx
similarity index 97%
rename from x-pack/plugins/uptime/public/components/fleet_package/http_advanced_fields.tsx
rename to x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.tsx
index 568ff526efb6e..aeaa452c38db9 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/http_advanced_fields.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.tsx
@@ -20,15 +20,15 @@ import {
EuiFieldPassword,
} from '@elastic/eui';
-import { useHTTPAdvancedFieldsContext } from './contexts';
+import { useHTTPAdvancedFieldsContext } from '../contexts';
-import { ConfigKeys, HTTPMethod, Validation } from './types';
+import { ConfigKeys, HTTPMethod, Validation } from '../types';
-import { OptionalLabel } from './optional_label';
-import { HeaderField } from './header_field';
-import { RequestBodyField } from './request_body_field';
-import { ResponseBodyIndexField } from './index_response_body_field';
-import { ComboBox } from './combo_box';
+import { OptionalLabel } from '../optional_label';
+import { HeaderField } from '../header_field';
+import { RequestBodyField } from '../request_body_field';
+import { ResponseBodyIndexField } from '../index_response_body_field';
+import { ComboBox } from '../combo_box';
interface Props {
validate: Validation;
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/http/simple_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/http/simple_fields.tsx
new file mode 100644
index 0000000000000..d17b8c997e9e8
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/http/simple_fields.tsx
@@ -0,0 +1,200 @@
+/*
+ * Copyright 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, { memo } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiFormRow, EuiFieldText, EuiFieldNumber } from '@elastic/eui';
+import { ConfigKeys, Validation } from '../types';
+import { useHTTPSimpleFieldsContext } from '../contexts';
+import { ComboBox } from '../combo_box';
+import { OptionalLabel } from '../optional_label';
+import { ScheduleField } from '../schedule_field';
+
+interface Props {
+ validate: Validation;
+}
+
+export const HTTPSimpleFields = memo(({ validate }) => {
+ const { fields, setFields } = useHTTPSimpleFieldsContext();
+ const handleInputChange = ({ value, configKey }: { value: unknown; configKey: ConfigKeys }) => {
+ setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
+ };
+
+ return (
+ <>
+
+ }
+ isInvalid={!!validate[ConfigKeys.URLS]?.(fields[ConfigKeys.URLS])}
+ error={
+
+ }
+ >
+
+ handleInputChange({ value: event.target.value, configKey: ConfigKeys.URLS })
+ }
+ data-test-subj="syntheticsUrlField"
+ />
+
+
+ }
+ isInvalid={!!validate[ConfigKeys.SCHEDULE]?.(fields[ConfigKeys.SCHEDULE])}
+ error={
+
+ }
+ >
+
+ handleInputChange({
+ value: schedule,
+ configKey: ConfigKeys.SCHEDULE,
+ })
+ }
+ number={fields[ConfigKeys.SCHEDULE].number}
+ unit={fields[ConfigKeys.SCHEDULE].unit}
+ />
+
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.APM_SERVICE_NAME,
+ })
+ }
+ data-test-subj="syntheticsAPMServiceName"
+ />
+
+
+ }
+ isInvalid={!!validate[ConfigKeys.MAX_REDIRECTS]?.(fields[ConfigKeys.MAX_REDIRECTS])}
+ error={
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.MAX_REDIRECTS,
+ })
+ }
+ />
+
+
+ }
+ isInvalid={
+ !!validate[ConfigKeys.TIMEOUT]?.(
+ fields[ConfigKeys.TIMEOUT],
+ fields[ConfigKeys.SCHEDULE].number,
+ fields[ConfigKeys.SCHEDULE].unit
+ )
+ }
+ error={
+
+ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.TIMEOUT,
+ })
+ }
+ step={'any'}
+ />
+
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+ handleInputChange({ value, configKey: ConfigKeys.TAGS })}
+ data-test-subj="syntheticsTags"
+ />
+
+ >
+ );
+});
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx
new file mode 100644
index 0000000000000..3ca07c7067367
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx
@@ -0,0 +1,204 @@
+/*
+ * Copyright 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, { memo } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiFormRow, EuiFieldText, EuiFieldNumber } from '@elastic/eui';
+import { ConfigKeys, Validation } from '../types';
+import { useICMPSimpleFieldsContext } from '../contexts';
+import { ComboBox } from '../combo_box';
+import { OptionalLabel } from '../optional_label';
+import { ScheduleField } from '../schedule_field';
+
+interface Props {
+ validate: Validation;
+}
+
+export const ICMPSimpleFields = memo(({ validate }) => {
+ const { fields, setFields } = useICMPSimpleFieldsContext();
+ const handleInputChange = ({ value, configKey }: { value: unknown; configKey: ConfigKeys }) => {
+ setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
+ };
+
+ return (
+ <>
+
+ }
+ isInvalid={!!validate[ConfigKeys.HOSTS]?.(fields[ConfigKeys.HOSTS])}
+ error={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.HOSTS,
+ })
+ }
+ data-test-subj="syntheticsICMPHostField"
+ />
+
+
+ }
+ isInvalid={!!validate[ConfigKeys.SCHEDULE]?.(fields[ConfigKeys.SCHEDULE])}
+ error={
+
+ }
+ >
+
+ handleInputChange({
+ value: schedule,
+ configKey: ConfigKeys.SCHEDULE,
+ })
+ }
+ number={fields[ConfigKeys.SCHEDULE].number}
+ unit={fields[ConfigKeys.SCHEDULE].unit}
+ />
+
+
+ }
+ isInvalid={!!validate[ConfigKeys.WAIT]?.(fields[ConfigKeys.WAIT])}
+ error={
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.WAIT,
+ })
+ }
+ step={'any'}
+ />
+
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.APM_SERVICE_NAME,
+ })
+ }
+ data-test-subj="syntheticsAPMServiceName"
+ />
+
+
+ }
+ isInvalid={
+ !!validate[ConfigKeys.TIMEOUT]?.(
+ fields[ConfigKeys.TIMEOUT],
+ fields[ConfigKeys.SCHEDULE].number,
+ fields[ConfigKeys.SCHEDULE].unit
+ )
+ }
+ error={
+
+ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.TIMEOUT,
+ })
+ }
+ step={'any'}
+ />
+
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+ handleInputChange({ value, configKey: ConfigKeys.TAGS })}
+ data-test-subj="syntheticsTags"
+ />
+
+ >
+ );
+});
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension.tsx
index 1306308f8ba4e..90e7e7d7bb733 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension.tsx
@@ -9,37 +9,62 @@ import React, { memo, useContext, useEffect } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import { PackagePolicyCreateExtensionComponentProps } from '../../../../fleet/public';
import { useTrackPageview } from '../../../../observability/public';
-import { Config, ConfigKeys, DataStream } from './types';
+import { PolicyConfig, DataStream } from './types';
import {
- SimpleFieldsContext,
+ MonitorTypeContext,
HTTPAdvancedFieldsContext,
TCPAdvancedFieldsContext,
TLSFieldsContext,
+ HTTPSimpleFieldsContext,
+ TCPSimpleFieldsContext,
+ ICMPSimpleFieldsContext,
+ defaultHTTPAdvancedFields,
+ defaultHTTPSimpleFields,
+ defaultICMPSimpleFields,
+ defaultTCPSimpleFields,
+ defaultTCPAdvancedFields,
+ defaultTLSFields,
} from './contexts';
import { CustomFields } from './custom_fields';
import { useUpdatePolicy } from './use_update_policy';
import { validate } from './validation';
+export const defaultConfig: PolicyConfig = {
+ [DataStream.HTTP]: {
+ ...defaultHTTPSimpleFields,
+ ...defaultHTTPAdvancedFields,
+ ...defaultTLSFields,
+ },
+ [DataStream.TCP]: {
+ ...defaultTCPSimpleFields,
+ ...defaultTCPAdvancedFields,
+ ...defaultTLSFields,
+ },
+ [DataStream.ICMP]: defaultICMPSimpleFields,
+};
+
/**
* Exports Synthetics-specific package policy instructions
* for use in the Ingest app create / edit package policy
*/
export const SyntheticsPolicyCreateExtension = memo(
({ newPolicy, onChange }) => {
- const { fields: simpleFields } = useContext(SimpleFieldsContext);
+ const { monitorType } = useContext(MonitorTypeContext);
+ const { fields: httpSimpleFields } = useContext(HTTPSimpleFieldsContext);
+ const { fields: tcpSimpleFields } = useContext(TCPSimpleFieldsContext);
+ const { fields: icmpSimpleFields } = useContext(ICMPSimpleFieldsContext);
const { fields: httpAdvancedFields } = useContext(HTTPAdvancedFieldsContext);
const { fields: tcpAdvancedFields } = useContext(TCPAdvancedFieldsContext);
const { fields: tlsFields } = useContext(TLSFieldsContext);
- const defaultConfig: Config = {
- name: '',
- ...simpleFields,
- ...httpAdvancedFields,
- ...tcpAdvancedFields,
- ...tlsFields,
- };
useTrackPageview({ app: 'fleet', path: 'syntheticsCreate' });
useTrackPageview({ app: 'fleet', path: 'syntheticsCreate', delay: 15000 });
- const { config, setConfig } = useUpdatePolicy({ defaultConfig, newPolicy, onChange, validate });
+ const { setConfig } = useUpdatePolicy({
+ monitorType,
+ defaultConfig,
+ newPolicy,
+ onChange,
+ validate,
+ });
// Fleet will initialize the create form with a default name for the integratin policy, however,
// for synthetics, we want the user to explicitely type in a name to use as the monitor name,
@@ -57,24 +82,40 @@ export const SyntheticsPolicyCreateExtension = memo {
- setConfig((prevConfig) => ({
- ...prevConfig,
- ...simpleFields,
- ...httpAdvancedFields,
- ...tcpAdvancedFields,
- ...tlsFields,
- // ensure proxyUrl is not overwritten
- [ConfigKeys.PROXY_URL]:
- simpleFields[ConfigKeys.MONITOR_TYPE] === DataStream.HTTP
- ? httpAdvancedFields[ConfigKeys.PROXY_URL]
- : tcpAdvancedFields[ConfigKeys.PROXY_URL],
- }));
+ setConfig(() => {
+ switch (monitorType) {
+ case DataStream.HTTP:
+ return {
+ ...httpSimpleFields,
+ ...httpAdvancedFields,
+ ...tlsFields,
+ };
+ case DataStream.TCP:
+ return {
+ ...tcpSimpleFields,
+ ...tcpAdvancedFields,
+ ...tlsFields,
+ };
+ case DataStream.ICMP:
+ return {
+ ...icmpSimpleFields,
+ };
+ }
+ });
},
250,
- [setConfig, simpleFields, httpAdvancedFields, tcpAdvancedFields, tlsFields]
+ [
+ setConfig,
+ httpSimpleFields,
+ tcpSimpleFields,
+ icmpSimpleFields,
+ httpAdvancedFields,
+ tcpAdvancedFields,
+ tlsFields,
+ ]
);
- return ;
+ return ;
}
);
SyntheticsPolicyCreateExtension.displayName = 'SyntheticsPolicyCreateExtension';
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx
index a16f2ba87d79a..395b5d67abeb0 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx
@@ -9,22 +9,10 @@ import React from 'react';
import { fireEvent, waitFor } from '@testing-library/react';
import { render } from '../../lib/helper/rtl_helpers';
import { NewPackagePolicy } from '../../../../fleet/public';
-import {
- defaultSimpleFields,
- defaultTLSFields,
- defaultHTTPAdvancedFields,
- defaultTCPAdvancedFields,
-} from './contexts';
import { SyntheticsPolicyCreateExtensionWrapper } from './synthetics_policy_create_extension_wrapper';
+import { defaultConfig } from './synthetics_policy_create_extension';
import { ConfigKeys, DataStream, ScheduleUnit, VerificationMode } from './types';
-const defaultConfig = {
- ...defaultSimpleFields,
- ...defaultTLSFields,
- ...defaultHTTPAdvancedFields,
- ...defaultTCPAdvancedFields,
-};
-
// ensures that fields appropriately match to their label
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
htmlIdGenerator: () => () => `id-${Math.random()}`,
@@ -266,6 +254,9 @@ const defaultNewPolicy: NewPackagePolicy = {
},
};
+const defaultHTTPConfig = defaultConfig[DataStream.HTTP];
+const defaultTCPConfig = defaultConfig[DataStream.TCP];
+
describe(' ', () => {
const onChange = jest.fn();
const WrappedComponent = ({ newPolicy = defaultNewPolicy }) => {
@@ -283,21 +274,21 @@ describe(' ', () => {
const maxRedirects = getByLabelText('Max redirects') as HTMLInputElement;
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
expect(monitorType).toBeInTheDocument();
- expect(monitorType.value).toEqual(defaultConfig[ConfigKeys.MONITOR_TYPE]);
+ expect(monitorType.value).toEqual(DataStream.HTTP);
expect(url).toBeInTheDocument();
- expect(url.value).toEqual(defaultConfig[ConfigKeys.URLS]);
+ expect(url.value).toEqual(defaultHTTPConfig[ConfigKeys.URLS]);
expect(proxyUrl).toBeInTheDocument();
- expect(proxyUrl.value).toEqual(defaultConfig[ConfigKeys.PROXY_URL]);
+ expect(proxyUrl.value).toEqual(defaultHTTPConfig[ConfigKeys.PROXY_URL]);
expect(monitorIntervalNumber).toBeInTheDocument();
- expect(monitorIntervalNumber.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].number);
+ expect(monitorIntervalNumber.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].number);
expect(monitorIntervalUnit).toBeInTheDocument();
- expect(monitorIntervalUnit.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].unit);
+ expect(monitorIntervalUnit.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].unit);
expect(apmServiceName).toBeInTheDocument();
- expect(apmServiceName.value).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ expect(apmServiceName.value).toEqual(defaultHTTPConfig[ConfigKeys.APM_SERVICE_NAME]);
expect(maxRedirects).toBeInTheDocument();
- expect(maxRedirects.value).toEqual(`${defaultConfig[ConfigKeys.MAX_REDIRECTS]}`);
+ expect(maxRedirects.value).toEqual(`${defaultHTTPConfig[ConfigKeys.MAX_REDIRECTS]}`);
expect(timeout).toBeInTheDocument();
- expect(timeout.value).toEqual(`${defaultConfig[ConfigKeys.TIMEOUT]}`);
+ expect(timeout.value).toEqual(`${defaultHTTPConfig[ConfigKeys.TIMEOUT]}`);
// ensure other monitor type options are not in the DOM
expect(queryByLabelText('Host')).not.toBeInTheDocument();
@@ -425,7 +416,7 @@ describe(' ', () => {
const { getByText, getByLabelText, queryByLabelText } = render( );
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
expect(monitorType).toBeInTheDocument();
- expect(monitorType.value).toEqual(defaultConfig[ConfigKeys.MONITOR_TYPE]);
+ expect(monitorType.value).toEqual(DataStream.HTTP);
fireEvent.change(monitorType, { target: { value: DataStream.TCP } });
await waitFor(() => {
@@ -452,7 +443,7 @@ describe(' ', () => {
const host = getByLabelText('Host:Port') as HTMLInputElement;
expect(host).toBeInTheDocument();
- expect(host.value).toEqual(defaultConfig[ConfigKeys.HOSTS]);
+ expect(host.value).toEqual(defaultTCPConfig[ConfigKeys.HOSTS]);
// expect HTTP fields not to be in the DOM
expect(queryByLabelText('URL')).not.toBeInTheDocument();
@@ -467,29 +458,6 @@ describe(' ', () => {
fireEvent.change(monitorType, { target: { value: DataStream.ICMP } });
- await waitFor(() => {
- expect(onChange).toBeCalledWith({
- isValid: false,
- updatedPolicy: {
- ...defaultNewPolicy,
- inputs: [
- {
- ...defaultNewPolicy.inputs[0],
- enabled: false,
- },
- {
- ...defaultNewPolicy.inputs[1],
- enabled: false,
- },
- {
- ...defaultNewPolicy.inputs[2],
- enabled: true,
- },
- ],
- },
- });
- });
-
// expect ICMP fields to be in the DOM
expect(getByLabelText('Wait in seconds')).toBeInTheDocument();
@@ -721,23 +689,27 @@ describe(' ', () => {
await waitFor(() => {
fireEvent.change(ca, { target: { value: 'certificateAuthorities' } });
- expect(ca.value).toEqual(defaultConfig[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES].value);
+ expect(ca.value).toEqual(defaultHTTPConfig[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES].value);
});
await waitFor(() => {
fireEvent.change(clientCertificate, { target: { value: 'clientCertificate' } });
- expect(clientCertificate.value).toEqual(defaultConfig[ConfigKeys.TLS_KEY].value);
+ expect(clientCertificate.value).toEqual(defaultHTTPConfig[ConfigKeys.TLS_KEY].value);
});
await waitFor(() => {
fireEvent.change(clientKey, { target: { value: 'clientKey' } });
- expect(clientKey.value).toEqual(defaultConfig[ConfigKeys.TLS_KEY].value);
+ expect(clientKey.value).toEqual(defaultHTTPConfig[ConfigKeys.TLS_KEY].value);
});
await waitFor(() => {
fireEvent.change(clientKeyPassphrase, { target: { value: 'clientKeyPassphrase' } });
- expect(clientKeyPassphrase.value).toEqual(defaultConfig[ConfigKeys.TLS_KEY_PASSPHRASE].value);
+ expect(clientKeyPassphrase.value).toEqual(
+ defaultHTTPConfig[ConfigKeys.TLS_KEY_PASSPHRASE].value
+ );
});
await waitFor(() => {
fireEvent.change(verificationMode, { target: { value: VerificationMode.NONE } });
- expect(verificationMode.value).toEqual(defaultConfig[ConfigKeys.TLS_VERIFICATION_MODE].value);
+ expect(verificationMode.value).toEqual(
+ defaultHTTPConfig[ConfigKeys.TLS_VERIFICATION_MODE].value
+ );
});
await waitFor(() => {
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx
index 688ee24bd2330..88bb8e7871459 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx
@@ -9,9 +9,10 @@ import React, { memo } from 'react';
import { PackagePolicyCreateExtensionComponentProps } from '../../../../fleet/public';
import { SyntheticsPolicyCreateExtension } from './synthetics_policy_create_extension';
import {
- SimpleFieldsContextProvider,
- HTTPAdvancedFieldsContextProvider,
- TCPAdvancedFieldsContextProvider,
+ MonitorTypeContextProvider,
+ TCPContextProvider,
+ ICMPSimpleFieldsContextProvider,
+ HTTPContextProvider,
TLSFieldsContextProvider,
} from './contexts';
@@ -22,15 +23,17 @@ import {
export const SyntheticsPolicyCreateExtensionWrapper = memo(
({ newPolicy, onChange }) => {
return (
-
-
-
+
+
+
-
+
+
+
-
-
-
+
+
+
);
}
);
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension.tsx
index e29a5c6a363ed..8a3c42c10bc14 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension.tsx
@@ -5,17 +5,20 @@
* 2.0.
*/
-import React, { memo, useContext } from 'react';
+import React, { memo } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import { PackagePolicyEditExtensionComponentProps } from '../../../../fleet/public';
import { useTrackPageview } from '../../../../observability/public';
import {
- SimpleFieldsContext,
- HTTPAdvancedFieldsContext,
- TCPAdvancedFieldsContext,
- TLSFieldsContext,
+ useMonitorTypeContext,
+ useTCPSimpleFieldsContext,
+ useTCPAdvancedFieldsContext,
+ useICMPSimpleFieldsContext,
+ useHTTPSimpleFieldsContext,
+ useHTTPAdvancedFieldsContext,
+ useTLSFieldsContext,
} from './contexts';
-import { Config, ConfigKeys, DataStream } from './types';
+import { PolicyConfig, DataStream } from './types';
import { CustomFields } from './custom_fields';
import { useUpdatePolicy } from './use_update_policy';
import { validate } from './validation';
@@ -23,7 +26,7 @@ import { validate } from './validation';
interface SyntheticsPolicyEditExtensionProps {
newPolicy: PackagePolicyEditExtensionComponentProps['newPolicy'];
onChange: PackagePolicyEditExtensionComponentProps['onChange'];
- defaultConfig: Config;
+ defaultConfig: PolicyConfig;
isTLSEnabled: boolean;
}
/**
@@ -34,37 +37,57 @@ export const SyntheticsPolicyEditExtension = memo {
useTrackPageview({ app: 'fleet', path: 'syntheticsEdit' });
useTrackPageview({ app: 'fleet', path: 'syntheticsEdit', delay: 15000 });
- const { fields: simpleFields } = useContext(SimpleFieldsContext);
- const { fields: httpAdvancedFields } = useContext(HTTPAdvancedFieldsContext);
- const { fields: tcpAdvancedFields } = useContext(TCPAdvancedFieldsContext);
- const { fields: tlsFields } = useContext(TLSFieldsContext);
- const { config, setConfig } = useUpdatePolicy({ defaultConfig, newPolicy, onChange, validate });
+ const { monitorType } = useMonitorTypeContext();
+ const { fields: httpSimpleFields } = useHTTPSimpleFieldsContext();
+ const { fields: tcpSimpleFields } = useTCPSimpleFieldsContext();
+ const { fields: icmpSimpleFields } = useICMPSimpleFieldsContext();
+ const { fields: httpAdvancedFields } = useHTTPAdvancedFieldsContext();
+ const { fields: tcpAdvancedFields } = useTCPAdvancedFieldsContext();
+ const { fields: tlsFields } = useTLSFieldsContext();
+ const { setConfig } = useUpdatePolicy({
+ defaultConfig,
+ newPolicy,
+ onChange,
+ validate,
+ monitorType,
+ });
useDebounce(
() => {
- setConfig((prevConfig) => ({
- ...prevConfig,
- ...simpleFields,
- ...httpAdvancedFields,
- ...tcpAdvancedFields,
- ...tlsFields,
- // ensure proxyUrl is not overwritten
- [ConfigKeys.PROXY_URL]:
- simpleFields[ConfigKeys.MONITOR_TYPE] === DataStream.HTTP
- ? httpAdvancedFields[ConfigKeys.PROXY_URL]
- : tcpAdvancedFields[ConfigKeys.PROXY_URL],
- }));
+ setConfig(() => {
+ switch (monitorType) {
+ case DataStream.HTTP:
+ return {
+ ...httpSimpleFields,
+ ...httpAdvancedFields,
+ ...tlsFields,
+ };
+ case DataStream.TCP:
+ return {
+ ...tcpSimpleFields,
+ ...tcpAdvancedFields,
+ ...tlsFields,
+ };
+ case DataStream.ICMP:
+ return {
+ ...icmpSimpleFields,
+ };
+ }
+ });
},
250,
- [setConfig, simpleFields, httpAdvancedFields, tcpAdvancedFields, tlsFields]
+ [
+ setConfig,
+ httpSimpleFields,
+ httpAdvancedFields,
+ tcpSimpleFields,
+ tcpAdvancedFields,
+ icmpSimpleFields,
+ tlsFields,
+ ]
);
- return (
-
- );
+ return ;
}
);
SyntheticsPolicyEditExtension.displayName = 'SyntheticsPolicyEditExtension';
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx
index e6981b9a850e1..fec6c504a445f 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx
@@ -11,25 +11,13 @@ import { render } from '../../lib/helper/rtl_helpers';
import { NewPackagePolicy } from '../../../../fleet/public';
import { SyntheticsPolicyEditExtensionWrapper } from './synthetics_policy_edit_extension_wrapper';
import { ConfigKeys, DataStream, ScheduleUnit } from './types';
-import {
- defaultSimpleFields,
- defaultTLSFields,
- defaultHTTPAdvancedFields,
- defaultTCPAdvancedFields,
-} from './contexts';
+import { defaultConfig } from './synthetics_policy_create_extension';
// ensures that fields appropriately match to their label
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
htmlIdGenerator: () => () => `id-${Math.random()}`,
}));
-const defaultConfig = {
- ...defaultSimpleFields,
- ...defaultTLSFields,
- ...defaultHTTPAdvancedFields,
- ...defaultTCPAdvancedFields,
-};
-
const defaultNewPolicy: NewPackagePolicy = {
name: 'samplePolicyName',
description: '',
@@ -277,6 +265,10 @@ const defaultCurrentPolicy: any = {
created_by: '',
};
+const defaultHTTPConfig = defaultConfig[DataStream.HTTP];
+const defaultICMPConfig = defaultConfig[DataStream.ICMP];
+const defaultTCPConfig = defaultConfig[DataStream.TCP];
+
describe(' ', () => {
const onChange = jest.fn();
const WrappedComponent = ({ policy = defaultCurrentPolicy, newPolicy = defaultNewPolicy }) => {
@@ -301,24 +293,24 @@ describe(' ', () => {
const verificationMode = getByLabelText('Verification mode') as HTMLInputElement;
const enableTLSConfig = getByLabelText('Enable TLS configuration') as HTMLInputElement;
expect(url).toBeInTheDocument();
- expect(url.value).toEqual(defaultConfig[ConfigKeys.URLS]);
+ expect(url.value).toEqual(defaultHTTPConfig[ConfigKeys.URLS]);
expect(proxyUrl).toBeInTheDocument();
- expect(proxyUrl.value).toEqual(defaultConfig[ConfigKeys.PROXY_URL]);
+ expect(proxyUrl.value).toEqual(defaultHTTPConfig[ConfigKeys.PROXY_URL]);
expect(monitorIntervalNumber).toBeInTheDocument();
- expect(monitorIntervalNumber.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].number);
+ expect(monitorIntervalNumber.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].number);
expect(monitorIntervalUnit).toBeInTheDocument();
- expect(monitorIntervalUnit.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].unit);
+ expect(monitorIntervalUnit.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].unit);
expect(apmServiceName).toBeInTheDocument();
- expect(apmServiceName.value).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ expect(apmServiceName.value).toEqual(defaultHTTPConfig[ConfigKeys.APM_SERVICE_NAME]);
expect(maxRedirects).toBeInTheDocument();
- expect(maxRedirects.value).toEqual(`${defaultConfig[ConfigKeys.MAX_REDIRECTS]}`);
+ expect(maxRedirects.value).toEqual(`${defaultHTTPConfig[ConfigKeys.MAX_REDIRECTS]}`);
expect(timeout).toBeInTheDocument();
- expect(timeout.value).toEqual(`${defaultConfig[ConfigKeys.TIMEOUT]}`);
+ expect(timeout.value).toEqual(`${defaultHTTPConfig[ConfigKeys.TIMEOUT]}`);
// expect TLS settings to be in the document when at least one tls key is populated
expect(enableTLSConfig.checked).toBe(true);
expect(verificationMode).toBeInTheDocument();
expect(verificationMode.value).toEqual(
- `${defaultConfig[ConfigKeys.TLS_VERIFICATION_MODE].value}`
+ `${defaultHTTPConfig[ConfigKeys.TLS_VERIFICATION_MODE].value}`
);
// ensure other monitor type options are not in the DOM
@@ -651,15 +643,21 @@ describe(' ', () => {
streams: [
{
...defaultNewPolicy.inputs[0].streams[0],
- vars: Object.keys(httpVars || []).reduce<
- Record
- >((acc, key) => {
- acc[key] = {
- value: undefined,
- type: `${httpVars?.[key].type}`,
- };
- return acc;
- }, {}),
+ vars: {
+ ...Object.keys(httpVars || []).reduce<
+ Record
+ >((acc, key) => {
+ acc[key] = {
+ value: undefined,
+ type: `${httpVars?.[key].type}`,
+ };
+ return acc;
+ }, {}),
+ [ConfigKeys.MONITOR_TYPE]: {
+ value: 'http',
+ type: 'text',
+ },
+ },
},
],
},
@@ -680,19 +678,19 @@ describe(' ', () => {
const enableTLSConfig = getByLabelText('Enable TLS configuration') as HTMLInputElement;
expect(url).toBeInTheDocument();
- expect(url.value).toEqual(defaultConfig[ConfigKeys.URLS]);
+ expect(url.value).toEqual(defaultHTTPConfig[ConfigKeys.URLS]);
expect(proxyUrl).toBeInTheDocument();
- expect(proxyUrl.value).toEqual(defaultConfig[ConfigKeys.PROXY_URL]);
+ expect(proxyUrl.value).toEqual(defaultHTTPConfig[ConfigKeys.PROXY_URL]);
expect(monitorIntervalNumber).toBeInTheDocument();
- expect(monitorIntervalNumber.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].number);
+ expect(monitorIntervalNumber.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].number);
expect(monitorIntervalUnit).toBeInTheDocument();
- expect(monitorIntervalUnit.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].unit);
+ expect(monitorIntervalUnit.value).toEqual(defaultHTTPConfig[ConfigKeys.SCHEDULE].unit);
expect(apmServiceName).toBeInTheDocument();
- expect(apmServiceName.value).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ expect(apmServiceName.value).toEqual(defaultHTTPConfig[ConfigKeys.APM_SERVICE_NAME]);
expect(maxRedirects).toBeInTheDocument();
- expect(maxRedirects.value).toEqual(`${defaultConfig[ConfigKeys.MAX_REDIRECTS]}`);
+ expect(maxRedirects.value).toEqual(`${defaultHTTPConfig[ConfigKeys.MAX_REDIRECTS]}`);
expect(timeout).toBeInTheDocument();
- expect(timeout.value).toEqual(`${defaultConfig[ConfigKeys.TIMEOUT]}`);
+ expect(timeout.value).toEqual(`${defaultHTTPConfig[ConfigKeys.TIMEOUT]}`);
/* expect TLS settings not to be in the document when and Enable TLS settings not to be checked
* when all TLS values are falsey */
@@ -709,7 +707,7 @@ describe(' ', () => {
await waitFor(() => {
const requestMethod = getByLabelText('Request method') as HTMLInputElement;
expect(requestMethod).toBeInTheDocument();
- expect(requestMethod.value).toEqual(`${defaultConfig[ConfigKeys.REQUEST_METHOD_CHECK]}`);
+ expect(requestMethod.value).toEqual(`${defaultHTTPConfig[ConfigKeys.REQUEST_METHOD_CHECK]}`);
});
});
@@ -752,24 +750,24 @@ describe(' ', () => {
const { getByText, getByLabelText, queryByLabelText } = render(
);
- const url = getByLabelText('Host:Port') as HTMLInputElement;
+ const host = getByLabelText('Host:Port') as HTMLInputElement;
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
const monitorIntervalUnit = getByLabelText('Unit') as HTMLInputElement;
const apmServiceName = getByLabelText('APM service name') as HTMLInputElement;
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
- expect(url).toBeInTheDocument();
- expect(url.value).toEqual(defaultConfig[ConfigKeys.URLS]);
+ expect(host).toBeInTheDocument();
+ expect(host.value).toEqual(defaultTCPConfig[ConfigKeys.HOSTS]);
expect(proxyUrl).toBeInTheDocument();
- expect(proxyUrl.value).toEqual(defaultConfig[ConfigKeys.PROXY_URL]);
+ expect(proxyUrl.value).toEqual(defaultTCPConfig[ConfigKeys.PROXY_URL]);
expect(monitorIntervalNumber).toBeInTheDocument();
- expect(monitorIntervalNumber.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].number);
+ expect(monitorIntervalNumber.value).toEqual(defaultTCPConfig[ConfigKeys.SCHEDULE].number);
expect(monitorIntervalUnit).toBeInTheDocument();
- expect(monitorIntervalUnit.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].unit);
+ expect(monitorIntervalUnit.value).toEqual(defaultTCPConfig[ConfigKeys.SCHEDULE].unit);
expect(apmServiceName).toBeInTheDocument();
- expect(apmServiceName.value).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ expect(apmServiceName.value).toEqual(defaultTCPConfig[ConfigKeys.APM_SERVICE_NAME]);
expect(timeout).toBeInTheDocument();
- expect(timeout.value).toEqual(`${defaultConfig[ConfigKeys.TIMEOUT]}`);
+ expect(timeout.value).toEqual(`${defaultTCPConfig[ConfigKeys.TIMEOUT]}`);
// ensure other monitor type options are not in the DOM
expect(queryByLabelText('Url')).not.toBeInTheDocument();
@@ -825,24 +823,24 @@ describe(' ', () => {
const { getByLabelText, queryByLabelText } = render(
);
- const url = getByLabelText('Host') as HTMLInputElement;
+ const host = getByLabelText('Host') as HTMLInputElement;
const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement;
const monitorIntervalUnit = getByLabelText('Unit') as HTMLInputElement;
const apmServiceName = getByLabelText('APM service name') as HTMLInputElement;
const timeout = getByLabelText('Timeout in seconds') as HTMLInputElement;
const wait = getByLabelText('Wait in seconds') as HTMLInputElement;
- expect(url).toBeInTheDocument();
- expect(url.value).toEqual(defaultConfig[ConfigKeys.URLS]);
+ expect(host).toBeInTheDocument();
+ expect(host.value).toEqual(defaultICMPConfig[ConfigKeys.HOSTS]);
expect(monitorIntervalNumber).toBeInTheDocument();
- expect(monitorIntervalNumber.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].number);
+ expect(monitorIntervalNumber.value).toEqual(defaultICMPConfig[ConfigKeys.SCHEDULE].number);
expect(monitorIntervalUnit).toBeInTheDocument();
- expect(monitorIntervalUnit.value).toEqual(defaultConfig[ConfigKeys.SCHEDULE].unit);
+ expect(monitorIntervalUnit.value).toEqual(defaultICMPConfig[ConfigKeys.SCHEDULE].unit);
expect(apmServiceName).toBeInTheDocument();
- expect(apmServiceName.value).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ expect(apmServiceName.value).toEqual(defaultICMPConfig[ConfigKeys.APM_SERVICE_NAME]);
expect(timeout).toBeInTheDocument();
- expect(timeout.value).toEqual(`${defaultConfig[ConfigKeys.TIMEOUT]}`);
+ expect(timeout.value).toEqual(`${defaultICMPConfig[ConfigKeys.TIMEOUT]}`);
expect(wait).toBeInTheDocument();
- expect(wait.value).toEqual(`${defaultConfig[ConfigKeys.WAIT]}`);
+ expect(wait.value).toEqual(`${defaultICMPConfig[ConfigKeys.WAIT]}`);
// ensure other monitor type options are not in the DOM
expect(queryByLabelText('Url')).not.toBeInTheDocument();
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx
index 85b38e05fdbc8..0bafef61166d2 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx
@@ -7,17 +7,26 @@
import React, { memo, useMemo } from 'react';
import { PackagePolicyEditExtensionComponentProps } from '../../../../fleet/public';
-import { Config, ConfigKeys, ContentType, contentTypesToMode } from './types';
+import {
+ PolicyConfig,
+ ConfigKeys,
+ ContentType,
+ DataStream,
+ ICustomFields,
+ contentTypesToMode,
+} from './types';
import { SyntheticsPolicyEditExtension } from './synthetics_policy_edit_extension';
import {
- SimpleFieldsContextProvider,
- HTTPAdvancedFieldsContextProvider,
- TCPAdvancedFieldsContextProvider,
- TLSFieldsContextProvider,
- defaultSimpleFields,
+ MonitorTypeContextProvider,
+ HTTPContextProvider,
+ TCPContextProvider,
+ defaultTCPSimpleFields,
+ defaultHTTPSimpleFields,
+ defaultICMPSimpleFields,
defaultHTTPAdvancedFields,
defaultTCPAdvancedFields,
defaultTLSFields,
+ ICMPSimpleFieldsContextProvider,
} from './contexts';
/**
@@ -26,21 +35,29 @@ import {
*/
export const SyntheticsPolicyEditExtensionWrapper = memo(
({ policy: currentPolicy, newPolicy, onChange }) => {
- const { enableTLS: isTLSEnabled, config: defaultConfig } = useMemo(() => {
- const fallbackConfig: Config = {
- name: '',
- ...defaultSimpleFields,
- ...defaultHTTPAdvancedFields,
- ...defaultTCPAdvancedFields,
- ...defaultTLSFields,
+ const { enableTLS: isTLSEnabled, config: defaultConfig, monitorType } = useMemo(() => {
+ const fallbackConfig: PolicyConfig = {
+ [DataStream.HTTP]: {
+ ...defaultHTTPSimpleFields,
+ ...defaultHTTPAdvancedFields,
+ ...defaultTLSFields,
+ },
+ [DataStream.TCP]: {
+ ...defaultTCPSimpleFields,
+ ...defaultTCPAdvancedFields,
+ ...defaultTLSFields,
+ },
+ [DataStream.ICMP]: defaultICMPSimpleFields,
};
let enableTLS = false;
const getDefaultConfig = () => {
const currentInput = currentPolicy.inputs.find((input) => input.enabled === true);
const vars = currentInput?.streams[0]?.vars;
+ const type: DataStream = vars?.[ConfigKeys.MONITOR_TYPE].value as DataStream;
+ const fallbackConfigForMonitorType = fallbackConfig[type] as Partial;
const configKeys: ConfigKeys[] = Object.values(ConfigKeys);
- const formattedDefaultConfig = configKeys.reduce(
+ const formatttedDefaultConfigForMonitorType = configKeys.reduce(
(acc: Record, key: ConfigKeys) => {
const value = vars?.[key]?.value;
switch (key) {
@@ -59,12 +76,14 @@ export const SyntheticsPolicyEditExtensionWrapper = memo {
if (
headerKey === 'Content-Type' &&
contentTypesToMode[headers[headerKey] as ContentType]
) {
- type = contentTypesToMode[headers[headerKey] as ContentType];
+ requestBodyType = contentTypesToMode[headers[headerKey] as ContentType];
return true;
}
});
acc[key] = {
value: requestBodyValue,
- type,
+ type: requestBodyType,
};
break;
case ConfigKeys.TLS_KEY_PASSPHRASE:
case ConfigKeys.TLS_VERIFICATION_MODE:
acc[key] = {
- value: value ?? fallbackConfig[key].value,
+ value: value ?? fallbackConfigForMonitorType[key]?.value,
isEnabled: !!value,
};
if (!!value) {
@@ -112,7 +131,7 @@ export const SyntheticsPolicyEditExtensionWrapper = memo
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
);
}
);
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/tcp_advanced_fields.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.test.tsx
similarity index 92%
rename from x-pack/plugins/uptime/public/components/fleet_package/tcp_advanced_fields.test.tsx
rename to x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.test.tsx
index 77551f9aa8011..78a6724fc8cfb 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/tcp_advanced_fields.test.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.test.tsx
@@ -7,13 +7,13 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
-import { render } from '../../lib/helper/rtl_helpers';
-import { TCPAdvancedFields } from './tcp_advanced_fields';
+import { render } from '../../../lib/helper/rtl_helpers';
+import { TCPAdvancedFields } from './advanced_fields';
import {
TCPAdvancedFieldsContextProvider,
defaultTCPAdvancedFields as defaultConfig,
-} from './contexts';
-import { ConfigKeys, ITCPAdvancedFields } from './types';
+} from '../contexts';
+import { ConfigKeys, ITCPAdvancedFields } from '../types';
// ensures fields and labels map appropriately
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/tcp_advanced_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.tsx
similarity index 97%
rename from x-pack/plugins/uptime/public/components/fleet_package/tcp_advanced_fields.tsx
rename to x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.tsx
index 161de0f0af8d0..9db07afa559b9 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/tcp_advanced_fields.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.tsx
@@ -16,11 +16,11 @@ import {
EuiSpacer,
} from '@elastic/eui';
-import { useTCPAdvancedFieldsContext } from './contexts';
+import { useTCPAdvancedFieldsContext } from '../contexts';
-import { ConfigKeys } from './types';
+import { ConfigKeys } from '../types';
-import { OptionalLabel } from './optional_label';
+import { OptionalLabel } from '../optional_label';
export const TCPAdvancedFields = () => {
const { fields, setFields } = useTCPAdvancedFieldsContext();
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx
new file mode 100644
index 0000000000000..82c77a63611f2
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx
@@ -0,0 +1,171 @@
+/*
+ * Copyright 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, { memo } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiFormRow, EuiFieldText, EuiFieldNumber } from '@elastic/eui';
+import { ConfigKeys, Validation } from '../types';
+import { useTCPSimpleFieldsContext } from '../contexts';
+import { ComboBox } from '../combo_box';
+import { OptionalLabel } from '../optional_label';
+import { ScheduleField } from '../schedule_field';
+
+interface Props {
+ validate: Validation;
+}
+
+export const TCPSimpleFields = memo(({ validate }) => {
+ const { fields, setFields } = useTCPSimpleFieldsContext();
+ const handleInputChange = ({ value, configKey }: { value: unknown; configKey: ConfigKeys }) => {
+ setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
+ };
+
+ return (
+ <>
+
+ }
+ isInvalid={!!validate[ConfigKeys.HOSTS]?.(fields[ConfigKeys.HOSTS])}
+ error={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.HOSTS,
+ })
+ }
+ data-test-subj="syntheticsTCPHostField"
+ />
+
+
+
+ }
+ isInvalid={!!validate[ConfigKeys.SCHEDULE]?.(fields[ConfigKeys.SCHEDULE])}
+ error={
+
+ }
+ >
+
+ handleInputChange({
+ value: schedule,
+ configKey: ConfigKeys.SCHEDULE,
+ })
+ }
+ number={fields[ConfigKeys.SCHEDULE].number}
+ unit={fields[ConfigKeys.SCHEDULE].unit}
+ />
+
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.APM_SERVICE_NAME,
+ })
+ }
+ data-test-subj="syntheticsAPMServiceName"
+ />
+
+
+ }
+ isInvalid={
+ !!validate[ConfigKeys.TIMEOUT]?.(
+ fields[ConfigKeys.TIMEOUT],
+ fields[ConfigKeys.SCHEDULE].number,
+ fields[ConfigKeys.SCHEDULE].unit
+ )
+ }
+ error={
+
+ }
+ helpText={
+
+ }
+ >
+
+ handleInputChange({
+ value: event.target.value,
+ configKey: ConfigKeys.TIMEOUT,
+ })
+ }
+ step={'any'}
+ />
+
+
+ }
+ labelAppend={ }
+ helpText={
+
+ }
+ >
+ handleInputChange({ value, configKey: ConfigKeys.TAGS })}
+ data-test-subj="syntheticsTags"
+ />
+
+ >
+ );
+});
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/types.tsx b/x-pack/plugins/uptime/public/components/fleet_package/types.tsx
index 802d5f08fd646..4d44b4f074e82 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/types.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/types.tsx
@@ -105,6 +105,28 @@ export interface ISimpleFields {
[ConfigKeys.WAIT]: string;
}
+export interface ICommonFields {
+ [ConfigKeys.MONITOR_TYPE]: DataStream;
+ [ConfigKeys.SCHEDULE]: { number: string; unit: ScheduleUnit };
+ [ConfigKeys.APM_SERVICE_NAME]: string;
+ [ConfigKeys.TIMEOUT]: string;
+ [ConfigKeys.TAGS]: string[];
+}
+
+export type IHTTPSimpleFields = {
+ [ConfigKeys.MAX_REDIRECTS]: string;
+ [ConfigKeys.URLS]: string;
+} & ICommonFields;
+
+export type ITCPSimpleFields = {
+ [ConfigKeys.HOSTS]: string;
+} & ICommonFields;
+
+export type IICMPSimpleFields = {
+ [ConfigKeys.HOSTS]: string;
+ [ConfigKeys.WAIT]: string;
+} & ICommonFields;
+
export interface ITLSFields {
[ConfigKeys.TLS_CERTIFICATE_AUTHORITIES]: {
value: string;
@@ -154,11 +176,21 @@ export interface ITCPAdvancedFields {
[ConfigKeys.REQUEST_SEND_CHECK]: string;
}
-export type ICustomFields = ISimpleFields & ITLSFields & IHTTPAdvancedFields & ITCPAdvancedFields;
+export type HTTPFields = IHTTPSimpleFields & IHTTPAdvancedFields & ITLSFields;
+export type TCPFields = ITCPSimpleFields & ITCPAdvancedFields & ITLSFields;
+export type ICMPFields = IICMPSimpleFields;
+
+export type ICustomFields = HTTPFields &
+ TCPFields &
+ ICMPFields & {
+ [ConfigKeys.NAME]: string;
+ };
-export type Config = {
- [ConfigKeys.NAME]: string;
-} & ICustomFields;
+export interface PolicyConfig {
+ [DataStream.HTTP]: HTTPFields;
+ [DataStream.TCP]: TCPFields;
+ [DataStream.ICMP]: ICMPFields;
+}
export type Validation = Partial void>>;
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx
index 3732791f895dc..5a62aec90032d 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.test.tsx
@@ -10,20 +10,7 @@ import { act, renderHook } from '@testing-library/react-hooks';
import { NewPackagePolicy } from '../../../../fleet/public';
import { validate } from './validation';
import { ConfigKeys, DataStream, TLSVersion } from './types';
-import {
- defaultSimpleFields,
- defaultTLSFields,
- defaultHTTPAdvancedFields,
- defaultTCPAdvancedFields,
-} from './contexts';
-
-const defaultConfig = {
- name: '',
- ...defaultSimpleFields,
- ...defaultTLSFields,
- ...defaultHTTPAdvancedFields,
- ...defaultTCPAdvancedFields,
-};
+import { defaultConfig } from './synthetics_policy_create_extension';
describe('useBarChartsHooks', () => {
const newPolicy: NewPackagePolicy = {
@@ -269,10 +256,10 @@ describe('useBarChartsHooks', () => {
it('handles http data stream', () => {
const onChange = jest.fn();
const { result } = renderHook((props) => useUpdatePolicy(props), {
- initialProps: { defaultConfig, newPolicy, onChange, validate },
+ initialProps: { defaultConfig, newPolicy, onChange, validate, monitorType: DataStream.HTTP },
});
- expect(result.current.config).toMatchObject({ ...defaultConfig });
+ expect(result.current.config).toMatchObject({ ...defaultConfig[DataStream.HTTP] });
// expect only http to be enabled
expect(result.current.updatedPolicy.inputs[0].enabled).toBe(true);
@@ -281,28 +268,28 @@ describe('useBarChartsHooks', () => {
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.MONITOR_TYPE].value
- ).toEqual(defaultConfig[ConfigKeys.MONITOR_TYPE]);
+ ).toEqual(defaultConfig[DataStream.HTTP][ConfigKeys.MONITOR_TYPE]);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.URLS].value
- ).toEqual(defaultConfig[ConfigKeys.URLS]);
+ ).toEqual(defaultConfig[DataStream.HTTP][ConfigKeys.URLS]);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.SCHEDULE].value
).toEqual(
JSON.stringify(
- `@every ${defaultConfig[ConfigKeys.SCHEDULE].number}${
- defaultConfig[ConfigKeys.SCHEDULE].unit
+ `@every ${defaultConfig[DataStream.HTTP][ConfigKeys.SCHEDULE].number}${
+ defaultConfig[DataStream.HTTP][ConfigKeys.SCHEDULE].unit
}`
)
);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.PROXY_URL].value
- ).toEqual(defaultConfig[ConfigKeys.PROXY_URL]);
+ ).toEqual(defaultConfig[DataStream.HTTP][ConfigKeys.PROXY_URL]);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.APM_SERVICE_NAME].value
- ).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ ).toEqual(defaultConfig[DataStream.HTTP][ConfigKeys.APM_SERVICE_NAME]);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.TIMEOUT].value
- ).toEqual(`${defaultConfig[ConfigKeys.TIMEOUT]}s`);
+ ).toEqual(`${defaultConfig[DataStream.HTTP][ConfigKeys.TIMEOUT]}s`);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[
ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE
@@ -316,29 +303,29 @@ describe('useBarChartsHooks', () => {
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.RESPONSE_STATUS_CHECK]
.value
- ).toEqual(JSON.stringify(defaultConfig[ConfigKeys.RESPONSE_STATUS_CHECK]));
+ ).toEqual(null);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.REQUEST_HEADERS_CHECK]
.value
- ).toEqual(JSON.stringify(defaultConfig[ConfigKeys.REQUEST_HEADERS_CHECK]));
+ ).toEqual(null);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.RESPONSE_HEADERS_CHECK]
.value
- ).toEqual(JSON.stringify(defaultConfig[ConfigKeys.RESPONSE_HEADERS_CHECK]));
+ ).toEqual(null);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.RESPONSE_BODY_INDEX]
.value
- ).toEqual(defaultConfig[ConfigKeys.RESPONSE_BODY_INDEX]);
+ ).toEqual(defaultConfig[DataStream.HTTP][ConfigKeys.RESPONSE_BODY_INDEX]);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.RESPONSE_HEADERS_INDEX]
.value
- ).toEqual(defaultConfig[ConfigKeys.RESPONSE_HEADERS_INDEX]);
+ ).toEqual(defaultConfig[DataStream.HTTP][ConfigKeys.RESPONSE_HEADERS_INDEX]);
});
it('stringifies array values and returns null for empty array values', () => {
const onChange = jest.fn();
const { result } = renderHook((props) => useUpdatePolicy(props), {
- initialProps: { defaultConfig, newPolicy, onChange, validate },
+ initialProps: { defaultConfig, newPolicy, onChange, validate, monitorType: DataStream.HTTP },
});
act(() => {
@@ -419,16 +406,8 @@ describe('useBarChartsHooks', () => {
it('handles tcp data stream', () => {
const onChange = jest.fn();
- const tcpConfig = {
- ...defaultConfig,
- [ConfigKeys.MONITOR_TYPE]: DataStream.TCP,
- };
const { result } = renderHook((props) => useUpdatePolicy(props), {
- initialProps: { defaultConfig, newPolicy, onChange, validate },
- });
-
- act(() => {
- result.current.setConfig(tcpConfig);
+ initialProps: { defaultConfig, newPolicy, onChange, validate, monitorType: DataStream.TCP },
});
// expect only tcp to be enabled
@@ -443,55 +422,47 @@ describe('useBarChartsHooks', () => {
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[ConfigKeys.MONITOR_TYPE].value
- ).toEqual(tcpConfig[ConfigKeys.MONITOR_TYPE]);
+ ).toEqual(defaultConfig[DataStream.TCP][ConfigKeys.MONITOR_TYPE]);
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[ConfigKeys.HOSTS].value
- ).toEqual(defaultConfig[ConfigKeys.HOSTS]);
+ ).toEqual(defaultConfig[DataStream.TCP][ConfigKeys.HOSTS]);
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[ConfigKeys.SCHEDULE].value
).toEqual(
JSON.stringify(
- `@every ${defaultConfig[ConfigKeys.SCHEDULE].number}${
- defaultConfig[ConfigKeys.SCHEDULE].unit
+ `@every ${defaultConfig[DataStream.TCP][ConfigKeys.SCHEDULE].number}${
+ defaultConfig[DataStream.TCP][ConfigKeys.SCHEDULE].unit
}`
)
);
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[ConfigKeys.PROXY_URL].value
- ).toEqual(tcpConfig[ConfigKeys.PROXY_URL]);
+ ).toEqual(defaultConfig[DataStream.TCP][ConfigKeys.PROXY_URL]);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.APM_SERVICE_NAME].value
- ).toEqual(tcpConfig[ConfigKeys.APM_SERVICE_NAME]);
+ ).toEqual(defaultConfig[DataStream.TCP][ConfigKeys.APM_SERVICE_NAME]);
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[ConfigKeys.TIMEOUT].value
- ).toEqual(`${tcpConfig[ConfigKeys.TIMEOUT]}s`);
+ ).toEqual(`${defaultConfig[DataStream.TCP][ConfigKeys.TIMEOUT]}s`);
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[
ConfigKeys.PROXY_USE_LOCAL_RESOLVER
].value
- ).toEqual(tcpConfig[ConfigKeys.PROXY_USE_LOCAL_RESOLVER]);
+ ).toEqual(defaultConfig[DataStream.TCP][ConfigKeys.PROXY_USE_LOCAL_RESOLVER]);
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[ConfigKeys.RESPONSE_RECEIVE_CHECK]
.value
- ).toEqual(tcpConfig[ConfigKeys.RESPONSE_RECEIVE_CHECK]);
+ ).toEqual(defaultConfig[DataStream.TCP][ConfigKeys.RESPONSE_RECEIVE_CHECK]);
expect(
result.current.updatedPolicy.inputs[1]?.streams[0]?.vars?.[ConfigKeys.REQUEST_SEND_CHECK]
.value
- ).toEqual(tcpConfig[ConfigKeys.REQUEST_SEND_CHECK]);
+ ).toEqual(defaultConfig[DataStream.TCP][ConfigKeys.REQUEST_SEND_CHECK]);
});
it('handles icmp data stream', () => {
const onChange = jest.fn();
- const icmpConfig = {
- ...defaultConfig,
- [ConfigKeys.MONITOR_TYPE]: DataStream.ICMP,
- };
const { result } = renderHook((props) => useUpdatePolicy(props), {
- initialProps: { defaultConfig, newPolicy, onChange, validate },
- });
-
- act(() => {
- result.current.setConfig(icmpConfig);
+ initialProps: { defaultConfig, newPolicy, onChange, validate, monitorType: DataStream.ICMP },
});
// expect only icmp to be enabled
@@ -506,25 +477,27 @@ describe('useBarChartsHooks', () => {
expect(
result.current.updatedPolicy.inputs[2]?.streams[0]?.vars?.[ConfigKeys.MONITOR_TYPE].value
- ).toEqual(icmpConfig[ConfigKeys.MONITOR_TYPE]);
+ ).toEqual(defaultConfig[DataStream.ICMP][ConfigKeys.MONITOR_TYPE]);
expect(
result.current.updatedPolicy.inputs[2]?.streams[0]?.vars?.[ConfigKeys.HOSTS].value
- ).toEqual(icmpConfig[ConfigKeys.HOSTS]);
+ ).toEqual(defaultConfig[DataStream.ICMP][ConfigKeys.HOSTS]);
expect(
result.current.updatedPolicy.inputs[2]?.streams[0]?.vars?.[ConfigKeys.SCHEDULE].value
).toEqual(
JSON.stringify(
- `@every ${icmpConfig[ConfigKeys.SCHEDULE].number}${icmpConfig[ConfigKeys.SCHEDULE].unit}`
+ `@every ${defaultConfig[DataStream.ICMP][ConfigKeys.SCHEDULE].number}${
+ defaultConfig[DataStream.ICMP][ConfigKeys.SCHEDULE].unit
+ }`
)
);
expect(
result.current.updatedPolicy.inputs[0]?.streams[0]?.vars?.[ConfigKeys.APM_SERVICE_NAME].value
- ).toEqual(defaultConfig[ConfigKeys.APM_SERVICE_NAME]);
+ ).toEqual(defaultConfig[DataStream.ICMP][ConfigKeys.APM_SERVICE_NAME]);
expect(
result.current.updatedPolicy.inputs[2]?.streams[0]?.vars?.[ConfigKeys.TIMEOUT].value
- ).toEqual(`${icmpConfig[ConfigKeys.TIMEOUT]}s`);
+ ).toEqual(`${defaultConfig[DataStream.ICMP][ConfigKeys.TIMEOUT]}s`);
expect(
result.current.updatedPolicy.inputs[2]?.streams[0]?.vars?.[ConfigKeys.WAIT].value
- ).toEqual(`${icmpConfig[ConfigKeys.WAIT]}s`);
+ ).toEqual(`${defaultConfig[DataStream.ICMP][ConfigKeys.WAIT]}s`);
});
});
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.ts b/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.ts
index cb11e9f9c4a9b..2b2fb22866463 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.ts
+++ b/x-pack/plugins/uptime/public/components/fleet_package/use_update_policy.ts
@@ -6,10 +6,11 @@
*/
import { useEffect, useRef, useState } from 'react';
import { NewPackagePolicy } from '../../../../fleet/public';
-import { ConfigKeys, Config, DataStream, Validation } from './types';
+import { ConfigKeys, PolicyConfig, DataStream, Validation, ICustomFields } from './types';
interface Props {
- defaultConfig: Config;
+ monitorType: DataStream;
+ defaultConfig: PolicyConfig;
newPolicy: NewPackagePolicy;
onChange: (opts: {
/** is current form state is valid */
@@ -20,22 +21,27 @@ interface Props {
validate: Record;
}
-export const useUpdatePolicy = ({ defaultConfig, newPolicy, onChange, validate }: Props) => {
+export const useUpdatePolicy = ({
+ monitorType,
+ defaultConfig,
+ newPolicy,
+ onChange,
+ validate,
+}: Props) => {
const [updatedPolicy, setUpdatedPolicy] = useState(newPolicy);
// Update the integration policy with our custom fields
- const [config, setConfig] = useState(defaultConfig);
- const currentConfig = useRef(defaultConfig);
+ const [config, setConfig] = useState>(defaultConfig[monitorType]);
+ const currentConfig = useRef>(defaultConfig[monitorType]);
useEffect(() => {
- const { type } = config;
const configKeys = Object.keys(config) as ConfigKeys[];
- const validationKeys = Object.keys(validate[type]) as ConfigKeys[];
+ const validationKeys = Object.keys(validate[monitorType]) as ConfigKeys[];
const configDidUpdate = configKeys.some((key) => config[key] !== currentConfig.current[key]);
const isValid =
- !!newPolicy.name && !validationKeys.find((key) => validate[type][key]?.(config[key]));
+ !!newPolicy.name && !validationKeys.find((key) => validate[monitorType][key]?.(config[key]));
const formattedPolicy = { ...newPolicy };
const currentInput = formattedPolicy.inputs.find(
- (input) => input.type === `synthetics/${type}`
+ (input) => input.type === `synthetics/${monitorType}`
);
const dataStream = currentInput?.streams[0];
@@ -51,17 +57,19 @@ export const useUpdatePolicy = ({ defaultConfig, newPolicy, onChange, validate }
if (configItem) {
switch (key) {
case ConfigKeys.SCHEDULE:
- configItem.value = JSON.stringify(`@every ${config[key].number}${config[key].unit}`); // convert to cron
+ configItem.value = JSON.stringify(
+ `@every ${config[key]?.number}${config[key]?.unit}`
+ ); // convert to cron
break;
case ConfigKeys.RESPONSE_BODY_CHECK_NEGATIVE:
case ConfigKeys.RESPONSE_BODY_CHECK_POSITIVE:
case ConfigKeys.RESPONSE_STATUS_CHECK:
case ConfigKeys.TAGS:
- configItem.value = config[key].length ? JSON.stringify(config[key]) : null;
+ configItem.value = config[key]?.length ? JSON.stringify(config[key]) : null;
break;
case ConfigKeys.RESPONSE_HEADERS_CHECK:
case ConfigKeys.REQUEST_HEADERS_CHECK:
- configItem.value = Object.keys(config[key]).length
+ configItem.value = Object.keys(config?.[key] || []).length
? JSON.stringify(config[key])
: null;
break;
@@ -70,26 +78,26 @@ export const useUpdatePolicy = ({ defaultConfig, newPolicy, onChange, validate }
configItem.value = config[key] ? `${config[key]}s` : null; // convert to cron
break;
case ConfigKeys.REQUEST_BODY_CHECK:
- configItem.value = config[key].value ? JSON.stringify(config[key].value) : null; // only need value of REQUEST_BODY_CHECK for outputted policy
+ configItem.value = config[key]?.value ? JSON.stringify(config[key]?.value) : null; // only need value of REQUEST_BODY_CHECK for outputted policy
break;
case ConfigKeys.TLS_CERTIFICATE:
case ConfigKeys.TLS_CERTIFICATE_AUTHORITIES:
case ConfigKeys.TLS_KEY:
configItem.value =
- config[key].isEnabled && config[key].value
- ? JSON.stringify(config[key].value)
+ config[key]?.isEnabled && config[key]?.value
+ ? JSON.stringify(config[key]?.value)
: null; // only add tls settings if they are enabled by the user
break;
case ConfigKeys.TLS_VERSION:
configItem.value =
- config[key].isEnabled && config[key].value.length
- ? JSON.stringify(config[key].value)
+ config[key]?.isEnabled && config[key]?.value.length
+ ? JSON.stringify(config[key]?.value)
: null; // only add tls settings if they are enabled by the user
break;
case ConfigKeys.TLS_KEY_PASSPHRASE:
case ConfigKeys.TLS_VERIFICATION_MODE:
configItem.value =
- config[key].isEnabled && config[key].value ? config[key].value : null; // only add tls settings if they are enabled by the user
+ config[key]?.isEnabled && config[key]?.value ? config[key]?.value : null; // only add tls settings if they are enabled by the user
break;
default:
configItem.value =
@@ -104,7 +112,7 @@ export const useUpdatePolicy = ({ defaultConfig, newPolicy, onChange, validate }
updatedPolicy: formattedPolicy,
});
}
- }, [config, currentConfig, newPolicy, onChange, validate]);
+ }, [config, currentConfig, newPolicy, onChange, validate, monitorType]);
// update our local config state ever time name, which is managed by fleet, changes
useEffect(() => {
diff --git a/x-pack/plugins/uptime/public/components/fleet_package/validation.tsx b/x-pack/plugins/uptime/public/components/fleet_package/validation.tsx
index 5197cb9299e45..f3057baf10381 100644
--- a/x-pack/plugins/uptime/public/components/fleet_package/validation.tsx
+++ b/x-pack/plugins/uptime/public/components/fleet_package/validation.tsx
@@ -48,10 +48,6 @@ function validateTimeout({
// validation functions return true when invalid
const validateCommon = {
- [ConfigKeys.MAX_REDIRECTS]: (value: unknown) =>
- (!!value && !`${value}`.match(digitsOnly)) ||
- parseFloat(value as ICustomFields[ConfigKeys.MAX_REDIRECTS]) < 0,
- [ConfigKeys.MONITOR_TYPE]: (value: unknown) => !value,
[ConfigKeys.SCHEDULE]: (value: unknown) => {
const { number, unit } = value as ICustomFields[ConfigKeys.SCHEDULE];
const parsedFloat = parseFloat(number);
@@ -84,6 +80,9 @@ const validateHTTP = {
const headers = value as ICustomFields[ConfigKeys.REQUEST_HEADERS_CHECK];
return validateHeaders(headers);
},
+ [ConfigKeys.MAX_REDIRECTS]: (value: unknown) =>
+ (!!value && !`${value}`.match(digitsOnly)) ||
+ parseFloat(value as ICustomFields[ConfigKeys.MAX_REDIRECTS]) < 0,
[ConfigKeys.URLS]: (value: unknown) => !value,
...validateCommon,
};
From dcb5a6708d5813b202bcc299860425d05e721f56 Mon Sep 17 00:00:00 2001
From: Janeen Mikell-Straughn <57149392+jmikell821@users.noreply.github.com>
Date: Wed, 16 Jun 2021 15:45:12 -0400
Subject: [PATCH 45/98] [DOCS] Updating Elastic Security Overview topic
(#101922)
* updating overview topic for Kibana
* formatting fixes
* small formatting tweaks
* small formatting tweaks
---
docs/siem/images/workflow.png | Bin 0 -> 308403 bytes
docs/siem/siem-ui.asciidoc | 238 +++++++++++++++++++++-------------
2 files changed, 148 insertions(+), 90 deletions(-)
create mode 100644 docs/siem/images/workflow.png
diff --git a/docs/siem/images/workflow.png b/docs/siem/images/workflow.png
new file mode 100644
index 0000000000000000000000000000000000000000..b71c7b0ace301e3554d71f4d2f156bbe476cbf10
GIT binary patch
literal 308403
zcma&O1yqz>*9HuTlu{0z0!m1CrAvtqex9iH0VF_Do*(r5E~k6SkSrkJM{$$zt*2Vsi4j(>P!B
zxs(TDm=ENXG1$@%G(3c7EEDa8(igbh$Ar-3lyjjYytONfzDPC4_Y=OiS%>{oF`@Ud
z9vLRV((m(w5R1?&Ys=R#$y131|C2ds0gMf6VL@Ud^gms;Di~_$lZH?y@Q=2d?U&%l
zw>S}hxX}D>8l*iAnZgSSm{>^%1c%qZ`yQDrB-y7E@m4
z|NF|>!R^IeI8mL*|6?ax^)b{#9PRB%i2gKkRPSv;s2fiX6B+7<`o2E>NMPFBKMD-d`qlJ5H5wq_z>d{*N&nLqgzAntDZmHgZk(}?lI+`J7z14
z8mW$XyT@8_oWh?htO9Um{Duec*c^UK#{K86wMLG_#>Valy%i(-;}YAb$6OEDtw%QL
zaQ(4CbyR~pQc+lc9?8c0U#-1&Y9QnMQ)|g#Z!<=c!>SaC|IoKUu4gFwCi~4*+x}_t
zYXvYL$(d5Ys1*Lcm!=G6{Uh|q5#s}gsk}cKc=BHsk_w%*>*)v>%fnUEq86O|dfk5^ppL%Xh;s*u>;tI(+5oJ=<0@FUHOra
zk>zP*OW_a@+*BB6Km)Y30bh%YAx=wDo!#AxoSZvMLtBmN2LR!&a$p01vrfKTpzBNWN2#}^kqEO71K_^2pX7J@i>>0LVQ
zklp(`Zr+qxGnMFuuJP_+KWyH!BP5rsm*t$;LlQ%jK`dWfwBU7uSKlW(
z!p24#L#U&|n>SAw85!pDrjMf#;?FAk@9ctg_I
zx2uOCxPXyz(-s}_=d$k^Z5OGNh{5CKVtz-=M=g*^}yO-Dc`=67yXuW*7eqsIr
zTft1|q-|=m5$+YUVV3QtH|8+pKNF5Pvx>pa7rwf>BBG^@rIYj#S^0Q9f?whqKXY8`o6xtob+_!N8%pa&WFnxBIK@SYhE>
zxE$%2xX7MKzRys}wA)+N?oB?7jE)9vbGz}i1E+OP&1C~Ak{76<`1BJx#$U79KcHz?
z{g@dZ2n-tAyWD#%qr34{(Bc2K?#3*p0aV7_f!UzxzpKiP5`#p9%}na%Y>k57`aa%Z
z>2n+2(80hv*cSaC*wj@e{myuF%8WKcUevo&(yl~yZ8LF86KY7EKMEtH-^J91YmGe~
z5{cX#D-<71S}QSWqGD0caLyBm&!-Tx>?K0Hh2NAnUp&FWr&^EGuYU7!E4p1M)MSx$
zmQU^x3rmlJC(nZrmNCzr*&iWGNsI!QMwOOda3_^Ay;QW4f!|%!pu7sFf_9T4>koAZrc?Um^@_1U{aK<7CoibxowaYaca`F6`tHbTgRBzd)1Bp}$(Ib;f*Wky$Nm
zy?e!1&}mUZST|0CH-q;v$wZ-AdRR#bhv)WcSM&k}Nv5|1g2x6%!y+H>K{<)*@M&J?
z|9}QRdsLW{7Ls%SJ7Cf#qtW)b9oLZ9u*tvJ`;?dz^eD52BnJU&g5l;5Byqa$8sSxw
zmp8z`R3q-W+n(;!yn<U)D=y`|gUe*bk=(-g_EO;NH%Axx8~0xfX{mx~GS??WVHiI>5Ox2Q3HG-0sIG
zs~VZE!h27{EyTMVTzg@#ZgcV1t?sVIZVuWdh0FekOwW43!|MVObx#4)WSpJLzER-+
zE%9IBIIxEKA#r_aLe#Rh}
zadUSqez!5tVxXfD-ZwW4{-6#!5^ia;UN}(fi
zu_-fRpcgM{s5uq_m=BM$$AjC)tmd>ncgeI{m0FBFJaQnhsl)14fAol1Bq6C>+Y8>v
z76Hm6$>@7MQ)%TW5vM@TuV5bqB^hAj8S5K_Y;YB%s4-+87Q7qgWu>Tci6w~@)rcCA
zC@`q3dw5v4DR<@PA+A;N$)ZgWu0SE2{mC8gf9y
zIfkC&MOvkJjU*aPiYxEizfG}Lya@U1{0u!Up3Q`>KPdJzi)Wj*!#671B!6U(*Gr=7
z<%t4%`rHibOOO$2cuoV|U{Ne&Q;Rlh$^nvWQL4yALM&tx&3`|Xd_hDai~tzb8v|I8
z8uTjYNQndPYV`01T}@QN97&$*NFcNU`8out6DttbjfJQu8H(20Zn{Ww}ulYoW(2;WFw#ViLp(QlFoi
z%O0M|7M7N3(Oi?VTy|Ln2{{ogg5sv$lbRmJ6$W1=W?y{*`DqK-$o44FgGr!E{te>K
zR&Y1S)52?_|0jiM?O|A!w_V~^pfBX7W&b<+jzDnsJSEaSms~yOVd)6GlZ9abZxaQc
zqWFln3M7%h%$lvL?)oaNOAD_y2D&UYpG;tbw>-)_s=oXKM5lr+&|^7%7s-YLos@t~
zoC=-{rB5{qdJQ-E?slZyS9IWkwS!*sX9eYPh3djG>&7^wdhM-Y!3sE(W3n!(W#+rX
z76IGQK$ie(i9>h1AvcXN+9BX{EgjyR8wChV6YXe!kkdN^7cFcObx!YDTqKrqk7XgA
z7G6_jeXp&I@Fy9nlae~!f+K3GyQ5YGdJr_;G@^Um81D(CuPUPPSLz?j`F6VhPg*cS
zhxwsxlsP5hjNr
zBd>~e>MXoS^ry;AIB(i9v^)`%#tjHx74hDsjyr`_w5X&r6YjOno*VOP~U8+`Vk4#m1S)3OAK1FNXXl
zi~g4mQ}lNYk)l&-Nls12Ndu6AoIx7vvX5OjY^@+Vj;ym@ruf=p7b%kPROu$yAJ3CV
z2Ty~#2scLxB8L1~4Ya4>em^;2C=a$pncCKzs;|bI3g$HU)@!8#@+bM)7qNCGRF*9#
zh!92vIf?W2M8_My;d7~X_wG8nzWgr&`zH?xJbAR7F>>`j?;p@MQir3t{7I(HeM?6-
zXV}yIYC*#WnIQ_w7*W+NFK16~XYI0~8}?^b&xrV}l3cH9-rOBy9#uoaC#lPaWKm6d
z`(q$kJU4Ci;M23j4F0ru{rPxDqo}c8L2#8_!H7)R^21%@j+j_8KOBUTE}w#1c>pZ?
z(q%D&-4wB(K_0rKUuDf8E`B-BH-ilnbZkDi{)IbQu6LXkd}?)I(cUv8Av4mMZQJn{
zyD7Xtqii=N(B*>XQtXszz(SX@b8&s}Kh-_iSS
z^CAqg#68Q>xldQN8eJBe?M0d6*D`9QV2Qx9vOv(XUz4D-sZ+B=QaOk^e!ts-em-0{
zuE(WGN}|RQ;c+nx2h@344!O$T4PUKU$)9~q;_myI$ZT0POJV+MV<^B(aVhX@w)3?z
zPJHc)f^zSF{4k2(@l(c-zFnYSL9IhRSGMN2e!b_fJ^zmRf$_4f05R@@drNsf^Vt?f
zEd2+f3Mx_Kjn(!f5>4H}jolwNoYY;Tr|F5pVV*P!lS#2Xmo=wb!*lT#>`tZCN!Kpf
zNp^@OWw&6IBa=OoyFR37?v~=}j4$W)K)p5FXjk%6UVSTqG2MH@>i>*_fQ-8VgMa#glDY5%Y9Sz&O+<+I-znf((5AcHv@{}v<%H9h^Q
z|6ABBQ^k1o>J=*&D;m;xbZEnty^%8Xt;{tp*s3`{t@>XNYyH8P(~`qY?j8G2aYw@Yq`20cRu!S!r~^EbtUjfn
zt_Gf){{txh3EpLo{-TQ5H#x_Q6msky6qS{=-Q2-;+1N`c!<8xi#YhCQr?e(~f3QxP
zA^&nXi`IwqD!uk=CEfrzM{Du0q?3YB;D?y|kC<8VyF)fRE>D5iQ6HH#ow{N@7b8tX
zMfV8zS{~Vn)p`;paq2}FN*%LdvK(uokEqBQ2mhOV{6>n-B3B#*He7irjbgcW6mq^B
z*$yO?w`WOS5(5AWCLKkl|5>wv9uME?8peKK_SmS6ZLlwLY9#xkTvS_+c(4ko6|Jz7fAA39#E
zwq<~vDK5jXwvKR77YQM*Mcid8Aro1DsVDvYBSXXVqY&N*4P|*fFFoopEzjRby}i+2
zfHC0e;K9@XaJC!0n2b9r=fQ-2Sl1VDxAoU=9RIqX6USaEAzEpY!wFIil>B^VXkrqD
zxH%CX*U?>D#HN>ry$wWkTEJy&3`u(MJr@Jlj|4uYJ+05pr3*jV=?I!ysz7Y^N@s?z
z^4~o>N~Zf(6z3LwI2{Bv1(V+O_Gad~BP%PrVUu^#iOS;kx2uYBh^Tc++v|S}XzA!g
z6?8LWhKE_K*FsXn?1b{RJGc6~qBC&RAm^pJCJJh5oQfJ5W~=U^I-L4ZrOZ*fGkvp;
z{tHCZOSC{1_4eN4?lu5Kob2_G;=dMt4*P?5fO_`kErY6muW@foDk>@$xSLds!*m1#
zlM+LfO*shT8!|4HjIEv8P|bl8Pfgy$LC)FB64OV|zNdG8jhKWcCb7qLKbrmq`|A4I
zWx)Z;D8)N!G-bAA9i4-&3tIceQ>nA`iwKm3qcicZ(mlhzhwQfJFccfMrG
z#KDN6JDeGv5$FaCubUf5U`j|>08}%}w>Sg|+}&%wgJpIxZRfKWZwZwy`_flN+c+5Hk$PEZ1cB*-Tn?Yd0?8^xWVqoWs<
zmsdr^+WPJIE-6GQxS?3QZKi
zN#r;>g?K9LM{!1qkzE2^-%d=3jvZXjdDaa1RI=d?5=dMa&s!&
zwgqcCT+>SXjPJa%3H1XEnm0KixIP$kf0N(PS$>`PZ<{}6dB}3~Sg8#C-|kn83Z3jH
z0i-=}MJs`oi^FYZkUWj)FduBF>HLlzr*{KCOj_7LN8s)<5SwsK#dG1BPEz~{bNBq%
zaZzz;Tf!aez3Dlur+x1b3qu^xPDUA)YY9k@3KBd9<%G}XD6J9+#`vdq2lySE3;eN
z2$D(+!0J$p?XK<9&OkGEV)R`6lGrQmg24-cm?R_X;$x$ga&T8jzER$JKuwQ0**BV9
zq(F*lQva4}FY^)YHQWcjQT`je{3qsbIAaC{2l1Z54c{$RvG`wHBiE`CSv{#^-el#Y
z%Tf4c`t|n-Z+}jScymle;m1Aovj{SL+Jrk$Em)111en1#D5p%KSNUx!_Kt-$O-$e>
z#jNGd7$?I4dr!CLs;On+p9lon>Px;&yJoUy%77V9DNMXiL`n3=71h)P-`J&Jex6I4
z8UFZ@R47~VXm^VI3+1L+LWiVyO|a-VehnCrRC=~AeS0s=Jr57S69<_C#8S5r*TxjK*2R+5
zdD?P=PmV2@n>JV0a!Am4am0T%bL#(=+wX9TLcpGNlEX0U_N-f&_j*<|#dnLd_U2@6
zzq2Et9c7g}mM7cz(jXAMw9oy%*RLt09JfVJkLyH#2L8Q0Tf-S!*<;8!4ML0?JnD+`
ztzj9FF){aR#fT;WhSuoWM{?u?!2cPk2B4~`208ry42rtcPF)9^(f>Wd>}JPNdk
zYl({&5?1=0^4hrLhs9A@wO7)&x3@bF#R@+hJ@~3YFCE7*$MQrBD_F^+N7#Q)a(kif
zULgo%8~}1`#udGTHQW3Q@k6tL{P-V~8c3+{wq-0oKY#zb3b#3m?kcJ*e(wlPY<>33
zgKuWm<+AB&8W_KFj>zsg+h61DuxJ`btpNiT6!+%NHT&dWHW*RB#l*VH*yozGR`02F
z%kb2w_#AiH)s#29^(G-lOIlejR0()r#0%6Zv
zud4?MC2uu#{wIO9WhS!B<8?w#p{g=Fay!8nX0q}uj_IvCa~XHi9PP5n-o)9CRSer*
zS*YmNfUL7mqq6>Sy5H^wMnvHF9VBLV7O16Hpfdf&<4MEb8m}_GhW^d!DU%Kq8S65@
z2{pSo+T4BQ;^G2{-b)d7qJ!yyacuUTY1!qwWQRc$JM6
zYBX@z;ti7Ij+HP!efA7D%NtI4wI3hQ`9-_XNM6arBnwbZiP%Tp-XPT@th5a>RG3Kc)Uw;Fr;TEMub=
ztMD#FzcYeMYxiq5MycU1XvQUtUZ4Dn(3W3KHua|rIP0L-)Y4kX6M(}qw^qf9N@>O1
zdKEk>^j36RE?yyW3V;_ax0fvtvo;LXbRm2;vh!;HYab5%%1+TjWCKzvrV0g(S=7yL
zuFhePhqhm%3}pYSj%$xBnq>OTKDw;v-Ixc_r<-HvB5r3m1oTpGw3t>>+=m1@yE~0?
zi@$&SE4Yi@2^=4PIqx>6mJ{tglqSf<6^wCIqF*(#@KbrObJL$)U~rm1@wKLXfu@>^
z*i6(tetMr@9C1LrlDRnptDcRuH6t%C@##E>kkxC97UhI9V^Lubb`tkS@;6}atUzol
z=k0x09r0sr8nVVLLEqgB#)cqU8CVqKsYLGsSYi}jTCS5;KT*^9530=p5#{8lGNigw
zl-2)yLJomA%n60)mtw(Xe9wnCxRkY0Ed)-%Co?wtHKOS=6xD4W?Faei7s~8L5-1j>
z@#wpX*U_jF#BK8h&uYFWauN1QjW{h7-`iDZ=R5J<;+BfA#T3;uMz<-SmTH6F!yXUj
z&n;>W(?cFtHMhk^Z)%U7nQ%Z-uD&DdE=|EcaB%e`*%=m!wF<~#3vuRcvx@6LUcNyNRW(EFej
z5gn_g)$3ugBX65!tTi#@!C2z>Z>T1p6N|I1PyFdBvG83=Mo8`1E-F=Y%Fp7~5XL`|
z{FGDC+iaOF00!#sE
z9ho>g;x)zP))-Y&UaKD%1=j?;8n((BjIabuZHPO*{(K@IqLh+DyJ;cDD$nm9oJMn)
zlM1-*1ZdQX#0xKFgfG9tK^1P6woFcU6drWkNL&grw<6Hf!|Ag#5hF>^
z6UWAtS%Ugmm|*G8pHzN`CI3hJb(u>#EOLj0MxI!Onb_K=zm4W02DwsBa~Yk=QdWHH
z@A?!Qv@xaV&b)$6mo5A9Y$3UI)v8WiJF-X}Wmbz&azv{4#rVMok-amU7e
zMa`13;^K~zow@y=#pSC%l1)9zGb=^D(u#YS^}b_B5qSOH7@cHRi`dqLaW4U9qr5IY
zL=F4jUe1UO(<{=--HP<<6ZX-OX){x-JM-iXBNdu%Row57-SLY`SV40g!`4!+4(`kt
zx*w7mSb1>T$nLgrC*UWU8yUa)5ryYMkJpi_lhPaAl~WgN%E8{>uW6{gwfeYL!%O}V
z2F9I?{rs{TSX_BfOSZ!6>1174X<74R$mGKigOf+xD^@zLvLXT+}N=o!CywF#fFwR7L95)BOt7gZueytD$2Pfg4kbqwzsI#fBi
zf)F)t2}riuxR1YlVcQjQ|0(Yo$uU#>-?7RlJ$#pu5#s{4CV(b>iW%Q?81!vMHea^3
z2MtRc4%P*Q=$e8BcUx}#QS53rsX*h+#TGF+IUofjxYBm}1#PdYYR9Sq%uX@uQU+hHCt)3e5;hP%^o_5Zn
zRaxQP@}@XIPn=k;sH8YGe_3YmqK<6@Fr!y8%<2%q^gW6yR9|SlK+w)1u7@~zgce{k
z*jZHEonP_|Q$zBC)x&Wnrk&qLmD{mh4Mk(Gwqw{ERa5=Sl5caL4hH@R2PzQ&yV|>i
zc69(_1H_~bThx*d63d&rCzH(>9hr1EOhWDmA^N6_f@9fr%@LCh%oVz3<7bc*mj$Co
zp9?&CRW#2VOj?k0S3Pq7RloV&$AHee-t)36)iTW|8Y)3F?3{YIyH^7e*?2DBIQdC|
zFWizTjkUGcPV%3ovE-4a&Qa+*Y~9Da66;|6hK1tXV9!t!00PCY#R@NVryr783};>I
zRl40Z@ph+E&+G?bxFd|}bRPdFnvUpT;%&Q5D+L_dNxV+(3`}oL^@{aW`v`N_1V>
zH>XpqY)|CmEk6JXvc1+d^lT_&Ny^yOa;`6*5}I#?#VS{DSm2HLdf4|k-EOg@+|Cv&
zSNuE~b%A&d@zDjG)89s-XoTC}LobMU}(lev5z4#jF{N
zj-8f~X2dI%q}5#0n+M#|#~)&8gJn_q#`4XbvnBdlisEFh!0fu})H`a)ppd$%#1}8@
z7a2rc#xb`QqFt`0c?G8Juupcn=mT!H$yM~)j!8F2cz9W0)%t1=4UwUq0&dVEpxP0qNYm5KJ;Tu0OS;hEY1h-d~
zqwNfY&Yg>eZt*lkJ;V6MO)I%;k7AJ1)SrS!#ODl)k0V+?YP9;(=T{U_Xh>fYZc97&
zau%hV3U|&W->h_Cjgb`moxLGd%qGNDDr_zZ=5JDm&2}B2V&>4i5}L!*wzDICJ*ewr
z>jken1%qj^v>u|{9W7_17x;8B$Lu<3UX28%*SntYCK{m8KfI3X$c@&!5X~ooF%-fC
z?#!%+`!o?Ow?{1Yde!}%tICaMX8;`|R$^)JIs=G-rJg%8(Z)FV_6jZyoLGI_yFD{D
zl-$$pj*M%N@|5mQXKH{za65Wpx`uDpu=V%?0s$2^CIPf2URN%N+2-R#-yuQTu+lBz
z3_~Y(wQ7*nm;(5ey!mY%ydUqITtHW7C=C62(DWusRCp<*jY+W{h^LNNtI+^cK^HGy
z_pE%NJw>r?IG3lp?xXZ10Zk9KAY2_mC;F!6Sccw^@UCqQ5$HwT24VVAoLc&@d&gNk@mhCU
zaYNk*F@oOV{5$DqE3!2$7e{Hyf2tGzl~Vkw{&if+C1d$zpidbPEJn`>uZ=5Q5nsD>
zk3w|G0&OqbK@PWVDIdp>{b{wl3|cCa?HTLz;u2ml`OHsme-0iK!iqq|`j~{Q$iP^BI6TNVbvAcd&K
zbRG9dvV#Sc-d|w61F(F|7vs@CxEWk0yb!XTk#Pv8F=G^nM#sX!6f>_~3|Vqu)-xoP
z;rY61>B#kd?0ZHp4IQz$`dSX$2UCr__CZe!ZClcug#S;H!KUNM!|qkUje36KMGnZc
z!F;ibP}uEI*k$_WR5n%@^L%D$201eUA6*ytQE=#QiZzYY3@~I*Jrj~{I4DXd75Tjw
z9C*TrB8g9aX6TQ_*zY(6t&N+A;#5ofP1FkouE8h0J;ubw2OP$#rE%yjj@|4Yr^v_%
zA0_11MO3L2`koBl5J}qV{)MK>of&{A!Qn{|t`_9#
znHBhXXpl1$DfjJn*7O^Bpa*HL=xdWza_h2$qIWzY*+XCK%Dak|;xux`xuTDu+-7eW
zogtWKPRgH*Ur1P-5pftb-tT*sgzb=;I$!pWYgT$Va-Rr{P3YQ%4J|Q6x+W&R(B>4%
ze_%LS+5vaB<8^@1B%?$zBjysvh0?&mkYXP96OI9ESlchgZ!qA;t9id+$p|4{@xog)
zd%dRFjKND(f)W!}@3RG@UG%z9P|f7O2V_?7e9I8LAY`pZNFg>Zny^{q--btaQdJj|
zDbS~u>f&|jvE!OLjv~iLY#9Yl#a;J^!eS^^ETq3WWcf1_CF@&*>|7{`wGxC*o2RSY
zsXZJQqOwj>cxgCPYhNEzIbR1W-j@!KVDtn6gDIPab#pi06%dvGJ;=Y%I;u#vps;b0
zR(pY}er||i%I$oIZDK
z&sSP&-Idg9@Vl!{Ck~IoOG^4s9~3$R_AxYXrXV59fZRtQG;Ma*=a3DAs64Tkk_3(i
z;PTpWkKW%1-cKwv%jmMFnQuU;84qwKIZUk`;XX98P(qR#Hj%^HEyo5@B?8)Z?tmj;
z42Aft(KI@<5K{4DW(2+agqP-?74RE26VIam4^n%BhRIB$g$7XJfM=HXf)P)?*!TIV
znI>Q05Ao|s>)IT4i(`t%uX
zs5wnz>*sn|T{(83&DDQu9|S6+4B+bf~~o&xnpkz0lBO{-Lfe44=}?q}fV$Jy-CT
zfIvoxp|yn}-hiaj^yYy42)RTbj7_Mhqmu-9A>L2QzE7s)a=il;x8c9L_6874S;~tj
zpyoB3=82wAmPt_1w=~c>+=Bo!HU{d#^|Yj;+88`m`2`-0GYr?X2YG^btETJy?;Mn%
z<+639h%A#-GLAV5@_`aHO7
zQw!cG1-=UDviX3*p8FKGEn6EY)-y!F;h|J!u
z@y&(6xItitS9J4>-?+HH{8!w;+~biRaxWYLP`_zcnyn=c{?i$H!D4OshG!=4D7Lb4hd~NqO0h$%wo5)mNN!#;7MhW
z)ghW-n0+&h%=R=`tyR6cIFO1HA=+;O1iCzh8y~vg&}L5*llwd>tfkrTiN;y6)8ZY4
zsSRC=LQ$p9G#sfB+_}L5>wFx^i?6uWm5!AyzUiSJr$INrlV=X9t=FLg1FTKb8FRI_
z1K*9CBI~xcAM|x#6XYRV(P2Mad4u@wb=X(hx5gIhthEK4BjP#a_{YjW72!C|StT_3
z9a<>Znqc&|3!#vCS1p@1mqAE@7mS-r79O^ej9Xo%l+##)-V^;5a_c5au>NsPPqsR8
zWP(+2f2R4;fT5S!^9v`f9imXiM7G3!6(66OOfP{s02Jf?NilLpJQd-zTZuL>p^8H&
z|HhuR@+#to&8th1Ct??k
zPg&`-@1J0<9GXa6hV<~Jq2wn9!Z$inzCrIH)Erq}xr!@dZEqOPgzy%cWApfEhf!+(
zGx4w0Q=%I1=YOZxC_nJa75D-++|+IHesXjlbTS3BqiV>!9tW177R!esY%@>Zx3rvT
z+rWs$ay@5U1UwWadxbfaatua=E&85fs
zbaGRcM{1G5)I+oS%DN0Y$Z!aPQ%MD~sfQbv@kQPN3}_z$r`xK}htMTN;Ni>IzN42@
zrFdm0jUkY#J0X;pS3_s@@tC!wsOxKRs9sGu5o85@Bb_90g_4)zJy~qbZY~bN$mOU%
zGR()35aaXb&nP-O3qZxj^EDC;S2IM)ZQ~tjgYW4J>MWY2CST|r>{6S|a1^*FGmh1C
zyJT)0$(jjslL%LmpI$1w0FQiJ^euh$f7Sc?CxLLgYq8Ovn?Z{