+
+
+
+
-
-
+
{this.getFeatureListLabel(this.state.selectedBasePrivilege.length > 0)}
@@ -338,7 +287,7 @@ export class PrivilegeSpaceForm extends Component {
buttonText = (
);
}
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx
index 64b7fe3e2e3a9..6bb9840fd343c 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx
@@ -23,7 +23,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { Space, getSpaceColor } from '../../../../../../../../spaces/public';
import { FeaturesPrivileges, Role, copyRole } from '../../../../../../../common/model';
-import { SpacesPopoverList } from '../../../spaces_popover_list';
import { PrivilegeDisplay } from './privilege_display';
import { isGlobalPrivilegeDefinition } from '../../../privilege_utils';
import { PrivilegeFormCalculator } from '../privilege_form_calculator';
@@ -118,19 +117,7 @@ export class PrivilegeSpaceTable extends Component {
const displayedSpaces = isExpanded ? spaces : spaces.slice(0, SPACES_DISPLAY_COUNT);
let button = null;
- if (record.isGlobal) {
- button = (
- s.id !== '*')}
- buttonText={i18n.translate(
- 'xpack.security.management.editRole.spacePrivilegeTable.showAllSpacesLink',
- {
- defaultMessage: 'show spaces',
- }
- )}
- />
- );
- } else if (spaces.length > displayedSpaces.length) {
+ if (spaces.length > displayedSpaces.length) {
button = (
{
name: i18n.translate(
'xpack.security.management.editRole.spaceAwarePrivilegeForm.globalSpacesName',
{
- defaultMessage: '* Global (all spaces)',
+ defaultMessage: '* All Spaces',
}
),
color: '#D3DAE6',
@@ -198,7 +198,7 @@ export class SpaceAwarePrivilegeSection extends Component {
>
);
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx
index 911a6b78a4944..95295b8e64732 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx
@@ -58,7 +58,7 @@ export class CustomizeSpace extends Component {
};
return (
-
+
diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
index ee1eb7c5e9aba..063a34091c4e7 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
@@ -2,10 +2,8 @@
exports[`EnabledFeatures renders as expected 1`] = `
{
return (
-
-
- hide
-
-
diff --git a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx
index 0b8085ff1ad16..576881398a63c 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx
@@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiLink } from '@elastic/eui';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { SectionPanel } from './section_panel';
test('it renders without blowing up', () => {
const wrapper = shallowWithIntl(
-
+
child
);
@@ -19,9 +18,9 @@ test('it renders without blowing up', () => {
expect(wrapper).toMatchSnapshot();
});
-test('it renders children by default', () => {
+test('it renders children', () => {
const wrapper = mountWithIntl(
-
+
child 1
child 2
@@ -30,19 +29,3 @@ test('it renders children by default', () => {
expect(wrapper.find(SectionPanel)).toHaveLength(1);
expect(wrapper.find('.child')).toHaveLength(2);
});
-
-test('it hides children when the "hide" link is clicked', () => {
- const wrapper = mountWithIntl(
-
- child 1
- child 2
-
- );
-
- expect(wrapper.find(SectionPanel)).toHaveLength(1);
- expect(wrapper.find('.child')).toHaveLength(2);
-
- wrapper.find(EuiLink).simulate('click');
-
- expect(wrapper.find('.child')).toHaveLength(0);
-});
diff --git a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx
index a6d25511acba6..492932aa95741 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx
@@ -8,39 +8,20 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
- EuiLink,
EuiPanel,
EuiSpacer,
EuiTitle,
IconType,
} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
import React, { Component, Fragment, ReactNode } from 'react';
interface Props {
iconType?: IconType;
title: string | ReactNode;
description: string;
- collapsible: boolean;
- initiallyCollapsed?: boolean;
}
-interface State {
- collapsed: boolean;
-}
-
-export class SectionPanel extends Component {
- public state = {
- collapsed: false,
- };
-
- constructor(props: Props) {
- super(props);
- this.state = {
- collapsed: props.initiallyCollapsed || false,
- };
- }
-
+export class SectionPanel extends Component {
public render() {
return (
@@ -51,30 +32,6 @@ export class SectionPanel extends Component {
}
public getTitle = () => {
- const showLinkText = i18n.translate('xpack.spaces.management.collapsiblePanel.showLinkText', {
- defaultMessage: 'show',
- });
-
- const hideLinkText = i18n.translate('xpack.spaces.management.collapsiblePanel.hideLinkText', {
- defaultMessage: 'hide',
- });
-
- const showLinkDescription = i18n.translate(
- 'xpack.spaces.management.collapsiblePanel.showLinkDescription',
- {
- defaultMessage: 'show {title}',
- values: { title: this.props.description },
- }
- );
-
- const hideLinkDescription = i18n.translate(
- 'xpack.spaces.management.collapsiblePanel.hideLinkDescription',
- {
- defaultMessage: 'hide {title}',
- values: { title: this.props.description },
- }
- );
-
return (
@@ -93,26 +50,11 @@ export class SectionPanel extends Component {
- {this.props.collapsible && (
-
-
- {this.state.collapsed ? showLinkText : hideLinkText}
-
-
- )}
);
};
public getForm = () => {
- if (this.state.collapsed) {
- return null;
- }
-
return (
@@ -120,10 +62,4 @@ export class SectionPanel extends Component {
);
};
-
- public toggleCollapsed = () => {
- this.setState({
- collapsed: !this.state.collapsed,
- });
- };
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index e344d18213ae5..f144cbd3873f7 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -14302,8 +14302,6 @@
"xpack.security.management.editRole.elasticSearchPrivileges.manageRoleActionsDescription": "このロールがクラスターに対して実行できる操作を管理します。 ",
"xpack.security.management.editRole.elasticSearchPrivileges.runAsPrivilegesTitle": "権限として実行",
"xpack.security.management.editRole.featureTable.customizeSubFeaturePrivilegesSwitchLabel": "サブ機能権限をカスタマイズする",
- "xpack.security.management.editRole.featureTable.enabledRoleFeaturesEnabledColumnTitle": "権限",
- "xpack.security.management.editRole.featureTable.enabledRoleFeaturesFeatureColumnTitle": "機能",
"xpack.security.management.editRole.featureTable.privilegeCustomizationTooltip": "機能でサブ機能の権限がカスタマイズされています。この行を展開すると詳細が表示されます。",
"xpack.security.management.editRole.indexPrivilegeForm.deleteSpacePrivilegeAriaLabel": "インデックスの権限を削除",
"xpack.security.management.editRole.indexPrivilegeForm.grantedDocumentsQueryFormRowLabel": "提供されたドキュメントのクエリ",
@@ -14345,35 +14343,24 @@
"xpack.security.management.editRole.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription": "利用可能なすべてのスペースを表示する権限がありません。",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.insufficientPrivilegesDescription": "権限が不十分です",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaAdminTitle": "kibana_admin",
- "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDetails": "選択されたスペースの全機能への完全アクセスを許可します。",
- "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDisplay": "すべて",
- "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDropdownDisplay": "すべて",
"xpack.security.management.editRole.spacePrivilegeForm.cancelButton": "キャンセル",
"xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivilegeDescription": "機能ごとに権限のレベルを上げます。機能によってはスペースごとに非表示になっているか、グローバルスペース権限による影響を受けているものもあります。",
"xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivileges": "機能ごとにカスタマイズ",
- "xpack.security.management.editRole.spacePrivilegeForm.customPrivilegeDetails": "選択されたスペースの機能ごとにアクセスをカスタマイズします",
- "xpack.security.management.editRole.spacePrivilegeForm.customPrivilegeDisplay": "カスタム",
- "xpack.security.management.editRole.spacePrivilegeForm.customPrivilegeDropdownDisplay": "カスタム",
"xpack.security.management.editRole.spacePrivilegeForm.featurePrivilegeSummaryDescription": "機能によってはスペースごとに非表示になっているか、グローバルスペース権限による影響を受けているものもあります。",
"xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeNotice": "これらの権限はすべての現在および未来のスペースに適用されます。",
"xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeWarning": "グローバル権限の作成は他のスペース権限に影響を与える可能性があります。",
"xpack.security.management.editRole.spacePrivilegeForm.modalTitle": "スペース権限",
"xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormLabel": "権限",
- "xpack.security.management.editRole.spacePrivilegeForm.readPrivilegeDetails": "選択されたスペースの全機能への読み込み専用アクセスを許可します。",
- "xpack.security.management.editRole.spacePrivilegeForm.readPrivilegeDisplay": "読み込み",
- "xpack.security.management.editRole.spacePrivilegeForm.readPrivilegeDropdownDisplay": "読み込み",
"xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormLabel": "スペース",
"xpack.security.management.editRole.spacePrivilegeForm.summaryOfFeaturePrivileges": "機能権限のサマリー",
"xpack.security.management.editRole.spacePrivilegeForm.supersededWarning": "宣言された権限は、構成済みグローバル権限よりも許容度が低くなります。権限サマリーを表示すると有効な権限がわかります。",
"xpack.security.management.editRole.spacePrivilegeForm.supersededWarningTitle": "グローバル権限に置き換え",
"xpack.security.management.editRole.spacePrivilegeMatrix.globalSpaceName": "グローバル",
- "xpack.security.management.editRole.spacePrivilegeMatrix.showAllSpacesLink": "(すべてのスペース)",
"xpack.security.management.editRole.spacePrivilegeMatrix.showNMoreSpacesLink": "他 {count} 件",
"xpack.security.management.editRole.spacePrivilegeSection.addSpacePrivilegeButton": "スペース権限を追加",
"xpack.security.management.editRole.spacePrivilegeSection.noAccessToKibanaTitle": "このロールは Kibana へのアクセスを許可しません",
"xpack.security.management.editRole.spacePrivilegeTable.deletePrivilegesLabel": "次のスペースの権限を削除: {spaceNames}",
"xpack.security.management.editRole.spacePrivilegeTable.editPrivilegesLabel": "次のスペースの権限を編集: {spaceNames}",
- "xpack.security.management.editRole.spacePrivilegeTable.showAllSpacesLink": "スペースを表示",
"xpack.security.management.editRole.spacePrivilegeTable.showLessSpacesLink": "縮小表示",
"xpack.security.management.editRole.spacePrivilegeTable.showNMoreSpacesLink": "他 {count} 件",
"xpack.security.management.editRole.spacePrivilegeTable.supersededPrivilegeWarning": "権限は、構成されたグローバル権限に置き換わります。権限サマリーを表示すると有効な権限がわかります。",
@@ -17360,10 +17347,6 @@
"xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription": "このページの設定は、別途指定されていない限り {spaceName}’スペースに適用されます。’",
"xpack.spaces.management.advancedSettingsTitle.settingsTitle": "設定",
"xpack.spaces.management.breadcrumb": "スペース",
- "xpack.spaces.management.collapsiblePanel.hideLinkDescription": "{title} を非表示",
- "xpack.spaces.management.collapsiblePanel.hideLinkText": "非表示",
- "xpack.spaces.management.collapsiblePanel.showLinkDescription": "{title} を表示",
- "xpack.spaces.management.collapsiblePanel.showLinkText": "表示",
"xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton": "キャンセル",
"xpack.spaces.management.confirmAlterActiveSpaceModal.reloadWarningMessage": "このスペースで表示される機能を更新しました。保存後にページが更新されます。",
"xpack.spaces.management.confirmAlterActiveSpaceModal.title": "スペースの更新の確認",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index ab7b558afbbf2..c4a599fa35e7f 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -14311,8 +14311,6 @@
"xpack.security.management.editRole.elasticSearchPrivileges.manageRoleActionsDescription": "管理此角色可以对您的集群执行的操作。 ",
"xpack.security.management.editRole.elasticSearchPrivileges.runAsPrivilegesTitle": "运行身份权限",
"xpack.security.management.editRole.featureTable.customizeSubFeaturePrivilegesSwitchLabel": "定制子功能权限",
- "xpack.security.management.editRole.featureTable.enabledRoleFeaturesEnabledColumnTitle": "权限",
- "xpack.security.management.editRole.featureTable.enabledRoleFeaturesFeatureColumnTitle": "功能",
"xpack.security.management.editRole.featureTable.privilegeCustomizationTooltip": "功能已定制子功能权限。展开此行以了解更多信息。",
"xpack.security.management.editRole.indexPrivilegeForm.deleteSpacePrivilegeAriaLabel": "删除索引权限",
"xpack.security.management.editRole.indexPrivilegeForm.grantedDocumentsQueryFormRowLabel": "已授权文档查询",
@@ -14354,35 +14352,24 @@
"xpack.security.management.editRole.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription": "您无权查看所有可用工作区。",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.insufficientPrivilegesDescription": "权限不足",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaAdminTitle": "kibana_admin",
- "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDetails": "授予对选定工作区所有功能的完全访问权限。",
- "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDisplay": "全部",
- "xpack.security.management.editRole.spacePrivilegeForm.allPrivilegeDropdownDisplay": "全部",
"xpack.security.management.editRole.spacePrivilegeForm.cancelButton": "取消",
"xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivilegeDescription": "按功能提高权限级别。某些功能可能被工作区隐藏或受全局工作区权限影响。",
"xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivileges": "按功能定制",
- "xpack.security.management.editRole.spacePrivilegeForm.customPrivilegeDetails": "在选定工作区中按功能定制访问权限。",
- "xpack.security.management.editRole.spacePrivilegeForm.customPrivilegeDisplay": "定制",
- "xpack.security.management.editRole.spacePrivilegeForm.customPrivilegeDropdownDisplay": "定制",
"xpack.security.management.editRole.spacePrivilegeForm.featurePrivilegeSummaryDescription": "某些功能可能被工作区隐藏或受全局工作区权限影响。",
"xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeNotice": "这些权限将应用到所有当前和未来工作区。",
"xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeWarning": "创建全局权限可能会影响您的其他工作区权限。",
"xpack.security.management.editRole.spacePrivilegeForm.modalTitle": "工作区权限",
"xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormLabel": "权限",
- "xpack.security.management.editRole.spacePrivilegeForm.readPrivilegeDetails": "授予对选定工作区所有功能的只读访问权限。",
- "xpack.security.management.editRole.spacePrivilegeForm.readPrivilegeDisplay": "读取",
- "xpack.security.management.editRole.spacePrivilegeForm.readPrivilegeDropdownDisplay": "读取",
"xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormLabel": "工作区",
"xpack.security.management.editRole.spacePrivilegeForm.summaryOfFeaturePrivileges": "功能权限的摘要",
"xpack.security.management.editRole.spacePrivilegeForm.supersededWarning": "声明的权限相对配置的全局权限有较小的宽容度。查看权限摘要以查看有效的权限。",
"xpack.security.management.editRole.spacePrivilegeForm.supersededWarningTitle": "已由全局权限取代",
"xpack.security.management.editRole.spacePrivilegeMatrix.globalSpaceName": "全局",
- "xpack.security.management.editRole.spacePrivilegeMatrix.showAllSpacesLink": "(所有工作区)",
"xpack.security.management.editRole.spacePrivilegeMatrix.showNMoreSpacesLink": "另外 {count} 个",
"xpack.security.management.editRole.spacePrivilegeSection.addSpacePrivilegeButton": "添加工作区权限",
"xpack.security.management.editRole.spacePrivilegeSection.noAccessToKibanaTitle": "此角色未授予对 Kibana 的访问权限",
"xpack.security.management.editRole.spacePrivilegeTable.deletePrivilegesLabel": "删除以下工作区的权限:{spaceNames}。",
"xpack.security.management.editRole.spacePrivilegeTable.editPrivilegesLabel": "编辑以下工作区的权限:{spaceNames}。",
- "xpack.security.management.editRole.spacePrivilegeTable.showAllSpacesLink": "显示工作区",
"xpack.security.management.editRole.spacePrivilegeTable.showLessSpacesLink": "显示更少",
"xpack.security.management.editRole.spacePrivilegeTable.showNMoreSpacesLink": "另外 {count} 个",
"xpack.security.management.editRole.spacePrivilegeTable.supersededPrivilegeWarning": "权限已由配置的全局权限取代。查看权限摘要以查看有效的权限。",
@@ -17370,10 +17357,6 @@
"xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription": "除非已指定,否则此页面上的设置适用于 {spaceName} 空间。",
"xpack.spaces.management.advancedSettingsTitle.settingsTitle": "设置",
"xpack.spaces.management.breadcrumb": "工作区",
- "xpack.spaces.management.collapsiblePanel.hideLinkDescription": "隐藏 {title}",
- "xpack.spaces.management.collapsiblePanel.hideLinkText": "隐藏",
- "xpack.spaces.management.collapsiblePanel.showLinkDescription": "显示 {title}",
- "xpack.spaces.management.collapsiblePanel.showLinkText": "显示",
"xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton": "取消",
"xpack.spaces.management.confirmAlterActiveSpaceModal.reloadWarningMessage": "您已更新此工作区中的可见功能。保存后,您的页面将重新加载。",
"xpack.spaces.management.confirmAlterActiveSpaceModal.title": "确认更新工作区",
diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts
index 3ce8a0e681d69..77457ad94cf88 100644
--- a/x-pack/test/functional/page_objects/security_page.ts
+++ b/x-pack/test/functional/page_objects/security_page.ts
@@ -429,10 +429,7 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider
const globalSpaceOption = await find.byCssSelector(`#spaceOption_\\*`);
await globalSpaceOption.click();
- await testSubjects.click('basePrivilegeComboBox');
-
- const privilegeOption = await find.byCssSelector(`#basePrivilege_${privilegeName}`);
- await privilegeOption.click();
+ await testSubjects.click(`basePrivilege_${privilegeName}`);
await testSubjects.click('createSpacePrivilegeButton');
}
From 79eb9b7b7a47c4859020b0b84db911143fbffbb4 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Fri, 2 Oct 2020 08:53:55 -0400
Subject: [PATCH 15/50] Use `process.executable` instead of `process.path`
(#79216)
---
.../common/endpoint/schema/trusted_apps.test.ts | 4 ++--
.../common/endpoint/schema/trusted_apps.ts | 2 +-
.../common/endpoint/types/trusted_apps.ts | 2 +-
.../components/condition_entry.tsx | 2 +-
.../components/trusted_app_card/index.stories.tsx | 4 ++--
.../routes/trusted_apps/trusted_apps.test.ts | 14 +++++++-------
6 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts
index 13a3fb96e10f7..ef1d9a99b0aeb 100644
--- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts
@@ -76,7 +76,7 @@ describe('When invoking Trusted Apps Schema', () => {
os: 'windows',
entries: [
{
- field: 'process.path.text',
+ field: 'process.executable.text',
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
@@ -204,7 +204,7 @@ describe('When invoking Trusted Apps Schema', () => {
field: 'process.hash.*',
value: 'A4370C0CF81686C0B696FA6261c9d3e0d810ae704ab8301839dffd5d5112f476',
},
- { field: 'process.path.text', value: '/tmp/dir1' },
+ { field: 'process.executable.text', value: '/tmp/dir1' },
].forEach((partialEntry) => {
const bodyMsg3 = {
...getCreateTrustedAppItem(),
diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts
index 912468b52adc0..25456115b3713 100644
--- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts
@@ -35,7 +35,7 @@ export const PostTrustedAppCreateRequestSchema = {
schema.object({
field: schema.oneOf([
schema.literal('process.hash.*'),
- schema.literal('process.path.text'),
+ schema.literal('process.executable.text'),
]),
type: schema.literal('match'),
operator: schema.literal('included'),
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts
index c0afe3b612d82..75e0347b10078 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts
@@ -33,7 +33,7 @@ export interface PostTrustedAppCreateResponse {
}
export interface MacosLinuxConditionEntry {
- field: 'process.hash.*' | 'process.path.text';
+ field: 'process.hash.*' | 'process.executable.text';
type: 'match';
operator: 'included';
value: string;
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx
index 7f7eae18b0816..7d30e81898cf2 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx
@@ -83,7 +83,7 @@ export const ConditionEntry = memo(
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path',
{ defaultMessage: 'Path' }
),
- value: 'process.path.text',
+ value: 'process.executable.text',
},
];
}, []);
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx
index 713e5e7095e12..4b64030a702c5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx
@@ -30,7 +30,7 @@ storiesOf('TrustedApps|TrustedAppCard', module)
trustedApp.created_at = '2020-09-17T14:52:33.899Z';
trustedApp.entries = [
{
- field: 'process.path.text',
+ field: 'process.executable.text',
operator: 'included',
type: 'match',
value: '/some/path/on/file/system',
@@ -44,7 +44,7 @@ storiesOf('TrustedApps|TrustedAppCard', module)
trustedApp.created_at = '2020-09-17T14:52:33.899Z';
trustedApp.entries = [
{
- field: 'process.path.text',
+ field: 'process.executable.text',
operator: 'included',
type: 'match',
value: '/some/path/on/file/system',
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts
index 98c9b79f32d6b..9e9a35ea35318 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts
@@ -240,7 +240,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
os: 'windows',
entries: [
{
- field: 'process.path.text',
+ field: 'process.executable.text',
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
@@ -293,7 +293,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
description: 'this one is ok',
entries: [
{
- field: 'process.path.text',
+ field: 'process.executable.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
@@ -320,7 +320,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
description: 'this one is ok',
entries: [
{
- field: 'process.path.text',
+ field: 'process.executable.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
@@ -357,7 +357,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
it('should trim condition entry values', async () => {
const newTrustedApp = createNewTrustedAppBody();
newTrustedApp.entries.push({
- field: 'process.path.text',
+ field: 'process.executable.text',
value: '\n some value \r\n ',
operator: 'included',
type: 'match',
@@ -366,13 +366,13 @@ describe('when invoking endpoint trusted apps route handlers', () => {
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
{
- field: 'process.path.text',
+ field: 'process.executable.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
},
{
- field: 'process.path.text',
+ field: 'process.executable.text',
value: 'some value',
operator: 'included',
type: 'match',
@@ -392,7 +392,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
{
- field: 'process.path.text',
+ field: 'process.executable.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
From 0628cfecf4ef2d0bab6525702d5a98404cd37509 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Fri, 2 Oct 2020 14:30:32 +0100
Subject: [PATCH 16/50] skip flaky suite (#79249)
---
.../spaces_only/tests/alerting/execution_status.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts
index ac63fe8faadc7..1c2e51637fb41 100644
--- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts
+++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts
@@ -19,7 +19,8 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context';
export default function executionStatusAlertTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
- describe('executionStatus', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/79249
+ describe.skip('executionStatus', () => {
const objectRemover = new ObjectRemover(supertest);
after(async () => await objectRemover.removeAll());
From a7d9e2f481c0d15b8fe6d8cedef881a310c23be7 Mon Sep 17 00:00:00 2001
From: Ryan Keairns
Date: Fri, 2 Oct 2020 08:39:37 -0500
Subject: [PATCH 17/50] Improved empty state for nav search (#79123)
* Improved empty state for nav search
* Updates tests to include required props
* Update empty state text
---
...tration_product_no_search_results_dark.svg | 1 +
...ration_product_no_search_results_light.svg | 1 +
.../public/components/search_bar.test.tsx | 21 +++++++-
.../public/components/search_bar.tsx | 51 ++++++++++++-------
.../global_search_bar/public/plugin.tsx | 20 ++++++--
5 files changed, 72 insertions(+), 22 deletions(-)
create mode 100644 x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_dark.svg
create mode 100644 x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_light.svg
diff --git a/x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_dark.svg b/x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_dark.svg
new file mode 100644
index 0000000000000..3a87f06b7bcc8
--- /dev/null
+++ b/x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_light.svg b/x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_light.svg
new file mode 100644
index 0000000000000..ac5298be17cca
--- /dev/null
+++ b/x-pack/plugins/global_search_bar/public/assets/illustration_product_no_search_results_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx
index 11fbc7931e620..6fad3335c5efc 100644
--- a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx
+++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx
@@ -8,6 +8,7 @@ import React from 'react';
import { wait } from '@testing-library/react';
import { of } from 'rxjs';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { httpServiceMock, uiSettingsServiceMock } from '../../../../../src/core/public/mocks';
import {
GlobalSearchBatchedResults,
GlobalSearchPluginStart,
@@ -47,6 +48,10 @@ const getSearchProps: any = (component: any) => component.find('EuiFieldSearch')
describe('SearchBar', () => {
let searchService: GlobalSearchPluginStart;
let findSpy: jest.SpyInstance;
+ const http = httpServiceMock.createSetupContract({ basePath: '/test' });
+ const basePathUrl = http.basePath.prepend('/plugins/globalSearchBar/assets/');
+ const uiSettings = uiSettingsServiceMock.createStartContract();
+ const darkMode = uiSettings.get('theme:darkMode');
beforeEach(() => {
searchService = globalSearchPluginMock.createStartContract();
@@ -66,7 +71,12 @@ describe('SearchBar', () => {
.mockReturnValueOnce(of(createBatch('Discover', { id: 'My Dashboard', type: 'test' })));
const component = mountWithIntl(
-
+
);
expect(findSpy).toHaveBeenCalledTimes(0);
@@ -85,7 +95,14 @@ describe('SearchBar', () => {
});
it('supports keyboard shortcuts', () => {
- mountWithIntl( );
+ mountWithIntl(
+
+ );
const searchEvent = new KeyboardEvent('keydown', {
key: '/',
diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
index 0dde28db0436d..4ca0f8cf81b7b 100644
--- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
+++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
@@ -12,6 +12,7 @@ import {
EuiSelectableTemplateSitewideOption,
EuiText,
EuiIcon,
+ EuiImage,
EuiHeaderSectionItemButton,
EuiSelectableMessage,
} from '@elastic/eui';
@@ -27,6 +28,8 @@ import { GlobalSearchPluginStart, GlobalSearchResult } from '../../../global_sea
interface Props {
globalSearch: GlobalSearchPluginStart['find'];
navigateToUrl: ApplicationStart['navigateToUrl'];
+ basePathUrl: string;
+ darkMode: boolean;
}
const clearField = (field: HTMLInputElement) => {
@@ -42,7 +45,7 @@ const clearField = (field: HTMLInputElement) => {
const cleanMeta = (str: string) => (str.charAt(0).toUpperCase() + str.slice(1)).replace(/-/g, ' ');
const blurEvent = new FocusEvent('blur');
-export function SearchBar({ globalSearch, navigateToUrl }: Props) {
+export function SearchBar({ globalSearch, navigateToUrl, basePathUrl, darkMode }: Props) {
const isMounted = useMountedState();
const [searchValue, setSearchValue] = useState('');
const [searchRef, setSearchRef] = useState(null);
@@ -134,6 +137,34 @@ export function SearchBar({ globalSearch, navigateToUrl }: Props) {
}
};
+ const emptyMessage = (
+
+
+
+
+
+
+
+
+
+
+
+ );
+
useEvent('keydown', onKeyDown);
return (
@@ -164,22 +195,8 @@ export function SearchBar({ globalSearch, navigateToUrl }: Props) {
popoverProps={{
repositionOnScroll: true,
}}
- emptyMessage={
-
-
-
-
-
-
-
-
- }
+ emptyMessage={emptyMessage}
+ noMatchesMessage={emptyMessage}
popoverFooter={
{
public start(core: CoreStart, { globalSearch }: GlobalSearchBarPluginStartDeps) {
core.chrome.navControls.registerCenter({
order: 1000,
- mount: (target) => this.mount(target, globalSearch, core.application.navigateToUrl),
+ mount: (target) =>
+ this.mount(
+ target,
+ globalSearch,
+ core.application.navigateToUrl,
+ core.http.basePath.prepend('/plugins/globalSearchBar/assets/'),
+ core.uiSettings.get('theme:darkMode')
+ ),
});
return {};
}
@@ -32,11 +39,18 @@ export class GlobalSearchBarPlugin implements Plugin<{}, {}> {
private mount(
targetDomElement: HTMLElement,
globalSearch: GlobalSearchPluginStart,
- navigateToUrl: ApplicationStart['navigateToUrl']
+ navigateToUrl: ApplicationStart['navigateToUrl'],
+ basePathUrl: string,
+ darkMode: boolean
) {
ReactDOM.render(
-
+
,
targetDomElement
);
From 95bf8750cda2937afbc022163a1d28c0d1326e3e Mon Sep 17 00:00:00 2001
From: Bohdan Tsymbala
Date: Fri, 2 Oct 2020 16:00:09 +0200
Subject: [PATCH 18/50] Refactored store code to group properties related to
location so that would be easy to introduce a new view type parameter.
(#79083)
---
.../public/management/common/routing.test.ts | 22 +++----
.../public/management/common/routing.ts | 35 ++++++-----
.../state/trusted_apps_list_page_state.ts | 12 ++--
.../trusted_apps/store/middleware.test.ts | 58 ++++++++++++------
.../pages/trusted_apps/store/middleware.ts | 12 ++--
.../pages/trusted_apps/store/reducer.test.ts | 14 ++---
.../pages/trusted_apps/store/reducer.ts | 33 ++++------
.../trusted_apps/store/selectors.test.ts | 58 +++++++-----------
.../pages/trusted_apps/store/selectors.ts | 60 ++++++-------------
.../pages/trusted_apps/test_utils/index.ts | 43 ++-----------
.../trusted_apps/view/trusted_apps_list.tsx | 8 +--
.../trusted_apps/view/trusted_apps_page.tsx | 17 +++---
12 files changed, 159 insertions(+), 213 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/common/routing.test.ts b/x-pack/plugins/security_solution/public/management/common/routing.test.ts
index 7a36654dcffc3..7082ab0ce5c4f 100644
--- a/x-pack/plugins/security_solution/public/management/common/routing.test.ts
+++ b/x-pack/plugins/security_solution/public/management/common/routing.test.ts
@@ -4,57 +4,57 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { extractListPaginationParams, getTrustedAppsListPath } from './routing';
+import { extractTrustedAppsListPageLocation, getTrustedAppsListPath } from './routing';
import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from './constants';
describe('routing', () => {
describe('extractListPaginationParams()', () => {
it('extracts default page index when not provided', () => {
- expect(extractListPaginationParams({}).page_index).toBe(MANAGEMENT_DEFAULT_PAGE);
+ expect(extractTrustedAppsListPageLocation({}).page_index).toBe(MANAGEMENT_DEFAULT_PAGE);
});
it('extracts default page index when too small value provided', () => {
- expect(extractListPaginationParams({ page_index: '-1' }).page_index).toBe(
+ expect(extractTrustedAppsListPageLocation({ page_index: '-1' }).page_index).toBe(
MANAGEMENT_DEFAULT_PAGE
);
});
it('extracts default page index when not a number provided', () => {
- expect(extractListPaginationParams({ page_index: 'a' }).page_index).toBe(
+ expect(extractTrustedAppsListPageLocation({ page_index: 'a' }).page_index).toBe(
MANAGEMENT_DEFAULT_PAGE
);
});
it('extracts only last page index when multiple values provided', () => {
- expect(extractListPaginationParams({ page_index: ['1', '2'] }).page_index).toBe(2);
+ expect(extractTrustedAppsListPageLocation({ page_index: ['1', '2'] }).page_index).toBe(2);
});
it('extracts proper page index when single valid value provided', () => {
- expect(extractListPaginationParams({ page_index: '2' }).page_index).toBe(2);
+ expect(extractTrustedAppsListPageLocation({ page_index: '2' }).page_index).toBe(2);
});
it('extracts default page size when not provided', () => {
- expect(extractListPaginationParams({}).page_size).toBe(MANAGEMENT_DEFAULT_PAGE_SIZE);
+ expect(extractTrustedAppsListPageLocation({}).page_size).toBe(MANAGEMENT_DEFAULT_PAGE_SIZE);
});
it('extracts default page size when invalid option provided', () => {
- expect(extractListPaginationParams({ page_size: '25' }).page_size).toBe(
+ expect(extractTrustedAppsListPageLocation({ page_size: '25' }).page_size).toBe(
MANAGEMENT_DEFAULT_PAGE_SIZE
);
});
it('extracts default page size when not a number provided', () => {
- expect(extractListPaginationParams({ page_size: 'a' }).page_size).toBe(
+ expect(extractTrustedAppsListPageLocation({ page_size: 'a' }).page_size).toBe(
MANAGEMENT_DEFAULT_PAGE_SIZE
);
});
it('extracts only last page size when multiple values provided', () => {
- expect(extractListPaginationParams({ page_size: ['10', '20'] }).page_size).toBe(20);
+ expect(extractTrustedAppsListPageLocation({ page_size: ['10', '20'] }).page_size).toBe(20);
});
it('extracts proper page size when single valid value provided', () => {
- expect(extractListPaginationParams({ page_size: '20' }).page_size).toBe(20);
+ expect(extractTrustedAppsListPageLocation({ page_size: '20' }).page_size).toBe(20);
});
});
diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts
index cb4ed9b098fce..9acf4a1613c0b 100644
--- a/x-pack/plugins/security_solution/public/management/common/routing.ts
+++ b/x-pack/plugins/security_solution/public/management/common/routing.ts
@@ -21,7 +21,7 @@ import {
import { AdministrationSubTab } from '../types';
import { appendSearch } from '../../common/components/link_to/helpers';
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
-import { TrustedAppsUrlParams } from '../pages/trusted_apps/types';
+import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state';
// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
type ExactKeys = Exclude extends never ? T1 : never;
@@ -94,18 +94,18 @@ const isDefaultOrMissing = (value: T | undefined, defaultValue: T) => {
return value === undefined || value === defaultValue;
};
-const normalizeListPaginationParams = (
- params?: Partial
-): Partial => {
- if (params) {
+const normalizeTrustedAppsPageLocation = (
+ location?: Partial
+): Partial => {
+ if (location) {
return {
- ...(!isDefaultOrMissing(params.page_index, MANAGEMENT_DEFAULT_PAGE)
- ? { page_index: params.page_index }
+ ...(!isDefaultOrMissing(location.page_index, MANAGEMENT_DEFAULT_PAGE)
+ ? { page_index: location.page_index }
: {}),
- ...(!isDefaultOrMissing(params.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE)
- ? { page_size: params.page_size }
+ ...(!isDefaultOrMissing(location.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE)
+ ? { page_size: location.page_size }
: {}),
- ...(!isDefaultOrMissing(params.show, undefined) ? { show: params.show } : {}),
+ ...(!isDefaultOrMissing(location.show, undefined) ? { show: location.show } : {}),
};
} else {
return {};
@@ -135,17 +135,22 @@ const extractPageSize = (query: querystring.ParsedUrlQuery): number => {
return MANAGEMENT_PAGE_SIZE_OPTIONS.includes(pageSize) ? pageSize : MANAGEMENT_DEFAULT_PAGE_SIZE;
};
-export const extractListPaginationParams = (
- query: querystring.ParsedUrlQuery
-): TrustedAppsUrlParams => ({
+export const extractListPaginationParams = (query: querystring.ParsedUrlQuery) => ({
page_index: extractPageIndex(query),
page_size: extractPageSize(query),
});
-export const getTrustedAppsListPath = (params?: Partial): string => {
+export const extractTrustedAppsListPageLocation = (
+ query: querystring.ParsedUrlQuery
+): TrustedAppsListPageLocation => ({
+ ...extractListPaginationParams(query),
+ show: extractFirstParamValue(query, 'show') === 'create' ? 'create' : undefined,
+});
+
+export const getTrustedAppsListPath = (params?: Partial): string => {
const path = generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, {
tabName: AdministrationSubTab.trustedApps,
});
- return `${path}${appendSearch(querystring.stringify(normalizeListPaginationParams(params)))}`;
+ return `${path}${appendSearch(querystring.stringify(normalizeTrustedAppsPageLocation(params)))}`;
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts
index 4c38ac0c4239a..a98ec03a006f5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts
@@ -7,7 +7,6 @@
import { ServerApiError } from '../../../../common/types';
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { AsyncResourceState } from '.';
-import { TrustedAppsUrlParams } from '../types';
export interface PaginationInfo {
index: number;
@@ -39,12 +38,16 @@ export interface TrustedAppCreateFailure {
data: ServerApiError;
}
+export interface TrustedAppsListPageLocation {
+ page_index: number;
+ page_size: number;
+ show?: 'create';
+}
+
export interface TrustedAppsListPageState {
listView: {
- currentListResourceState: AsyncResourceState;
- currentPaginationInfo: PaginationInfo;
+ listResourceState: AsyncResourceState;
freshDataTimestamp: number;
- show: TrustedAppsUrlParams['show'] | undefined;
};
deletionDialog: {
entry?: TrustedApp;
@@ -56,5 +59,6 @@ export interface TrustedAppsListPageState {
| TrustedAppCreatePending
| TrustedAppCreateSuccess
| TrustedAppCreateFailure;
+ location: TrustedAppsListPageLocation;
active: boolean;
}
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts
index 19c2d3a62781f..2143b5135c575 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts
@@ -9,13 +9,12 @@ import { applyMiddleware, createStore } from 'redux';
import { createSpyMiddleware } from '../../../../common/store/test_utils';
import {
- createFailedListViewWithPagination,
createListLoadedResourceState,
createLoadedListViewWithPagination,
- createLoadingListViewWithPagination,
createSampleTrustedApp,
createSampleTrustedApps,
createServerApiError,
+ createUninitialisedResourceState,
createUserChangedUrlAction,
} from '../test_utils';
@@ -76,6 +75,7 @@ describe('middleware', () => {
describe('refreshing list resource state', () => {
it('refreshes the list when location changes and data gets outdated', async () => {
const pagination = { index: 2, size: 50 };
+ const location = { page_index: 2, page_size: 50, show: undefined };
const service = createTrustedAppsServiceMock();
const { store, spyMiddleware } = createStoreSetup(service);
@@ -87,21 +87,30 @@ describe('middleware', () => {
expect(store.getState()).toStrictEqual({
...initialState,
- listView: createLoadingListViewWithPagination(initialNow, pagination),
+ listView: {
+ listResourceState: {
+ type: 'LoadingResourceState',
+ previousState: createUninitialisedResourceState(),
+ },
+ freshDataTimestamp: initialNow,
+ },
active: true,
+ location,
});
await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
expect(store.getState()).toStrictEqual({
...initialState,
- listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500),
+ listView: createLoadedListViewWithPagination(initialNow, pagination, 500),
active: true,
+ location,
});
});
it('does not refresh the list when location changes and data does not get outdated', async () => {
const pagination = { index: 2, size: 50 };
+ const location = { page_index: 2, page_size: 50, show: undefined };
const service = createTrustedAppsServiceMock();
const { store, spyMiddleware } = createStoreSetup(service);
@@ -118,14 +127,16 @@ describe('middleware', () => {
expect(service.getTrustedAppsList).toBeCalledTimes(1);
expect(store.getState()).toStrictEqual({
...initialState,
- listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500),
+ listView: createLoadedListViewWithPagination(initialNow, pagination, 500),
active: true,
+ location,
});
});
it('refreshes the list when data gets outdated with and outdate action', async () => {
const newNow = 222222;
const pagination = { index: 0, size: 10 };
+ const location = { page_index: 0, page_size: 10, show: undefined };
const service = createTrustedAppsServiceMock();
const { store, spyMiddleware } = createStoreSetup(service);
@@ -143,20 +154,24 @@ describe('middleware', () => {
expect(store.getState()).toStrictEqual({
...initialState,
- listView: createLoadingListViewWithPagination(
- newNow,
- pagination,
- createListLoadedResourceState(pagination, 500, initialNow)
- ),
+ listView: {
+ listResourceState: {
+ type: 'LoadingResourceState',
+ previousState: createListLoadedResourceState(pagination, 500, initialNow),
+ },
+ freshDataTimestamp: newNow,
+ },
active: true,
+ location,
});
await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged');
expect(store.getState()).toStrictEqual({
...initialState,
- listView: createLoadedListViewWithPagination(newNow, pagination, pagination, 500),
+ listView: createLoadedListViewWithPagination(newNow, pagination, 500),
active: true,
+ location,
});
});
@@ -172,12 +187,16 @@ describe('middleware', () => {
expect(store.getState()).toStrictEqual({
...initialState,
- listView: createFailedListViewWithPagination(
- initialNow,
- { index: 2, size: 50 },
- createServerApiError('Internal Server Error')
- ),
+ listView: {
+ listResourceState: {
+ type: 'FailedResourceState',
+ error: createServerApiError('Internal Server Error'),
+ lastLoadedState: undefined,
+ },
+ freshDataTimestamp: initialNow,
+ },
active: true,
+ location: { page_index: 2, page_size: 50, show: undefined },
});
const infiniteLoopTest = async () => {
@@ -193,10 +212,11 @@ describe('middleware', () => {
const entry = createSampleTrustedApp(3);
const notFoundError = createServerApiError('Not Found');
const pagination = { index: 0, size: 10 };
+ const location = { page_index: 0, page_size: 10, show: undefined };
const getTrustedAppsListResponse = createGetTrustedListAppsResponse(pagination, 500);
- const listView = createLoadedListViewWithPagination(initialNow, pagination, pagination, 500);
- const listViewNew = createLoadedListViewWithPagination(newNow, pagination, pagination, 500);
- const testStartState = { ...initialState, listView, active: true };
+ const listView = createLoadedListViewWithPagination(initialNow, pagination, 500);
+ const listViewNew = createLoadedListViewWithPagination(newNow, pagination, 500);
+ const testStartState = { ...initialState, listView, active: true, location };
it('does not submit when entry is undefined', async () => {
const service = createTrustedAppsServiceMock();
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts
index dd96c8d807048..9fa456dc5ffe2 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts
@@ -29,12 +29,12 @@ import {
} from './action';
import {
- getCurrentListResourceState,
+ getListResourceState,
getDeletionDialogEntry,
getDeletionSubmissionResourceState,
getLastLoadedListResourceState,
- getListCurrentPageIndex,
- getListCurrentPageSize,
+ getCurrentLocationPageIndex,
+ getCurrentLocationPageSize,
getTrustedAppCreateData,
isCreatePending,
needsRefreshOfListData,
@@ -56,15 +56,15 @@ const refreshListIfNeeded = async (
createTrustedAppsListResourceStateChangedAction({
type: 'LoadingResourceState',
// need to think on how to avoid the casting
- previousState: getCurrentListResourceState(store.getState()) as Immutable<
+ previousState: getListResourceState(store.getState()) as Immutable<
StaleResourceState
>,
})
);
try {
- const pageIndex = getListCurrentPageIndex(store.getState());
- const pageSize = getListCurrentPageSize(store.getState());
+ const pageIndex = getCurrentLocationPageIndex(store.getState());
+ const pageSize = getCurrentLocationPageSize(store.getState());
const response = await trustedAppsService.getTrustedAppsList({
page: pageIndex + 1,
per_page: pageSize,
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts
index 228f0932edd28..94fcdb39bb169 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts
@@ -32,17 +32,14 @@ describe('reducer', () => {
expect(result).toStrictEqual({
...initialState,
- listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } },
+ location: { page_index: 5, page_size: 50, show: undefined },
active: true,
});
});
it('extracts default pagination parameters when none provided', () => {
const result = trustedAppsPageReducer(
- {
- ...initialState,
- listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } },
- },
+ { ...initialState, location: { page_index: 5, page_size: 50 } },
createUserChangedUrlAction('/trusted_apps', '?page_index=b&page_size=60')
);
@@ -51,10 +48,7 @@ describe('reducer', () => {
it('extracts default pagination parameters when invalid provided', () => {
const result = trustedAppsPageReducer(
- {
- ...initialState,
- listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } },
- },
+ { ...initialState, location: { page_index: 5, page_size: 50 } },
createUserChangedUrlAction('/trusted_apps')
);
@@ -85,7 +79,7 @@ describe('reducer', () => {
expect(result).toStrictEqual({
...initialState,
- listView: { ...initialState.listView, currentListResourceState: listResourceState },
+ listView: { ...initialState.listView, listResourceState },
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts
index ec210254bf76f..f4056f02a4140 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts
@@ -11,7 +11,8 @@ import { ImmutableReducer } from '../../../../common/store';
import { AppLocation, Immutable } from '../../../../../common/endpoint/types';
import { UserChangedUrl } from '../../../../common/store/routing/action';
import { AppAction } from '../../../../common/store/actions';
-import { extractFirstParamValue, extractListPaginationParams } from '../../../common/routing';
+import { extractTrustedAppsListPageLocation } from '../../../common/routing';
+
import {
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
MANAGEMENT_DEFAULT_PAGE,
@@ -29,6 +30,7 @@ import {
ServerReturnedCreateTrustedAppSuccess,
UserClickedSaveNewTrustedAppButton,
} from './action';
+
import { TrustedAppsListPageState } from '../state';
type StateReducer = ImmutableReducer;
@@ -64,7 +66,7 @@ const trustedAppsListResourceStateChanged: CaseReducer = (state, action) => {
if (isTrustedAppsPageLocation(action.payload)) {
const parsedUrlsParams = parse(action.payload.search.slice(1));
- const paginationParams = extractListPaginationParams(parsedUrlsParams);
- const show =
- extractFirstParamValue(parsedUrlsParams, 'show') === 'create' ? 'create' : undefined;
+ const location = extractTrustedAppsListPageLocation(parsedUrlsParams);
return {
...state,
- listView: {
- ...state.listView,
- currentPaginationInfo: {
- index: paginationParams.page_index,
- size: paginationParams.page_size,
- },
- show,
- },
- createView: show ? state.createView : undefined,
+ createView: location.show ? state.createView : undefined,
active: true,
+ location,
};
} else {
return initialTrustedAppsPageState();
@@ -150,16 +143,16 @@ const initialDeletionDialogState = (): TrustedAppsListPageState['deletionDialog'
export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({
listView: {
- currentListResourceState: { type: 'UninitialisedResourceState' },
- currentPaginationInfo: {
- index: MANAGEMENT_DEFAULT_PAGE,
- size: MANAGEMENT_DEFAULT_PAGE_SIZE,
- },
+ listResourceState: { type: 'UninitialisedResourceState' },
freshDataTimestamp: Date.now(),
- show: undefined,
},
deletionDialog: initialDeletionDialogState(),
createView: undefined,
+ location: {
+ page_index: MANAGEMENT_DEFAULT_PAGE,
+ page_size: MANAGEMENT_DEFAULT_PAGE_SIZE,
+ show: undefined,
+ },
active: false,
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts
index 0be4d0b05acc4..01fe3e5bf202e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts
@@ -7,10 +7,10 @@
import { AsyncResourceState, TrustedAppsListPageState } from '../state';
import { initialTrustedAppsPageState } from './reducer';
import {
- getCurrentListResourceState,
+ getListResourceState,
getLastLoadedListResourceState,
- getListCurrentPageIndex,
- getListCurrentPageSize,
+ getCurrentLocationPageIndex,
+ getCurrentLocationPageSize,
getListErrorMessage,
getListItems,
getListTotalItemsCount,
@@ -25,7 +25,6 @@ import {
} from './selectors';
import {
- createDefaultListView,
createDefaultPaginationInfo,
createListComplexLoadingResourceState,
createListFailedResourceState,
@@ -85,16 +84,17 @@ describe('selectors', () => {
it('returns false when current loaded data is up to date', () => {
const listView = createLoadedListViewWithPagination(initialNow);
+ const location = { page_index: 0, page_size: 10 };
- expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(false);
+ expect(needsRefreshOfListData({ ...initialState, listView, active: true, location })).toBe(
+ false
+ );
});
});
- describe('getCurrentListResourceState()', () => {
+ describe('getListResourceState()', () => {
it('returns current list resource state', () => {
- const state = { ...initialState, listView: createDefaultListView(initialNow) };
-
- expect(getCurrentListResourceState(state)).toStrictEqual(createUninitialisedResourceState());
+ expect(getListResourceState(initialState)).toStrictEqual(createUninitialisedResourceState());
});
});
@@ -103,14 +103,12 @@ describe('selectors', () => {
const state = {
...initialState,
listView: {
- currentListResourceState: createListComplexLoadingResourceState(
+ listResourceState: createListComplexLoadingResourceState(
createDefaultPaginationInfo(),
200,
initialNow
),
- currentPaginationInfo: createDefaultPaginationInfo(),
freshDataTimestamp: initialNow,
- show: undefined,
},
};
@@ -122,23 +120,19 @@ describe('selectors', () => {
describe('getListItems()', () => {
it('returns empty list when no valid data loaded', () => {
- const state = { ...initialState, listView: createDefaultListView(initialNow) };
-
- expect(getListItems(state)).toStrictEqual([]);
+ expect(getListItems(initialState)).toStrictEqual([]);
});
it('returns last loaded list items', () => {
const state = {
...initialState,
listView: {
- currentListResourceState: createListComplexLoadingResourceState(
+ listResourceState: createListComplexLoadingResourceState(
createDefaultPaginationInfo(),
200,
initialNow
),
- currentPaginationInfo: createDefaultPaginationInfo(),
freshDataTimestamp: initialNow,
- show: undefined,
},
};
@@ -150,23 +144,19 @@ describe('selectors', () => {
describe('getListTotalItemsCount()', () => {
it('returns 0 when no valid data loaded', () => {
- const state = { ...initialState, listView: createDefaultListView(initialNow) };
-
- expect(getListTotalItemsCount(state)).toBe(0);
+ expect(getListTotalItemsCount(initialState)).toBe(0);
});
it('returns last loaded total items count', () => {
const state = {
...initialState,
listView: {
- currentListResourceState: createListComplexLoadingResourceState(
+ listResourceState: createListComplexLoadingResourceState(
createDefaultPaginationInfo(),
200,
initialNow
),
- currentPaginationInfo: createDefaultPaginationInfo(),
freshDataTimestamp: initialNow,
- show: undefined,
},
};
@@ -176,17 +166,17 @@ describe('selectors', () => {
describe('getListCurrentPageIndex()', () => {
it('returns page index', () => {
- const state = { ...initialState, listView: createDefaultListView(initialNow) };
+ const state = { ...initialState, location: { page_index: 3, page_size: 10 } };
- expect(getListCurrentPageIndex(state)).toBe(0);
+ expect(getCurrentLocationPageIndex(state)).toBe(3);
});
});
describe('getListCurrentPageSize()', () => {
it('returns page size', () => {
- const state = { ...initialState, listView: createDefaultListView(initialNow) };
+ const state = { ...initialState, location: { page_index: 0, page_size: 20 } };
- expect(getListCurrentPageSize(state)).toBe(20);
+ expect(getCurrentLocationPageSize(state)).toBe(20);
});
});
@@ -195,14 +185,12 @@ describe('selectors', () => {
const state = {
...initialState,
listView: {
- currentListResourceState: createListComplexLoadingResourceState(
+ listResourceState: createListComplexLoadingResourceState(
createDefaultPaginationInfo(),
200,
initialNow
),
- currentPaginationInfo: createDefaultPaginationInfo(),
freshDataTimestamp: initialNow,
- show: undefined,
},
};
@@ -213,10 +201,8 @@ describe('selectors', () => {
const state = {
...initialState,
listView: {
- currentListResourceState: createListFailedResourceState('Internal Server Error'),
- currentPaginationInfo: createDefaultPaginationInfo(),
+ listResourceState: createListFailedResourceState('Internal Server Error'),
freshDataTimestamp: initialNow,
- show: undefined,
},
};
@@ -233,14 +219,12 @@ describe('selectors', () => {
const state = {
...initialState,
listView: {
- currentListResourceState: createListComplexLoadingResourceState(
+ listResourceState: createListComplexLoadingResourceState(
createDefaultPaginationInfo(),
200,
initialNow
),
- currentPaginationInfo: createDefaultPaginationInfo(),
freshDataTimestamp: initialNow,
- show: undefined,
},
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts
index 6239b425efe2f..62ffa364e4a6c 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { createSelector } from 'reselect';
import { ServerApiError } from '../../../../common/types';
import { Immutable, NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types';
@@ -17,97 +16,76 @@ import {
isLoadingResourceState,
isOutdatedResourceState,
LoadedResourceState,
- PaginationInfo,
TrustedAppCreateFailure,
TrustedAppsListData,
+ TrustedAppsListPageLocation,
TrustedAppsListPageState,
} from '../state';
-import { TrustedAppsUrlParams } from '../types';
import {
isTrustedAppCreateFailureState,
isTrustedAppCreatePendingState,
isTrustedAppCreateSuccessState,
} from '../state/type_guards';
-const pageInfosEqual = (pageInfo1: PaginationInfo, pageInfo2: PaginationInfo): boolean =>
- pageInfo1.index === pageInfo2.index && pageInfo1.size === pageInfo2.size;
-
export const needsRefreshOfListData = (state: Immutable): boolean => {
- const currentPageInfo = state.listView.currentPaginationInfo;
- const currentPage = state.listView.currentListResourceState;
const freshDataTimestamp = state.listView.freshDataTimestamp;
+ const currentPage = state.listView.listResourceState;
+ const location = state.location;
return (
state.active &&
isOutdatedResourceState(currentPage, (data) => {
return (
- pageInfosEqual(currentPageInfo, data.paginationInfo) && data.timestamp >= freshDataTimestamp
+ data.paginationInfo.index === location.page_index &&
+ data.paginationInfo.size === location.page_size &&
+ data.timestamp >= freshDataTimestamp
);
})
);
};
-export const getCurrentListResourceState = (
+export const getListResourceState = (
state: Immutable
): Immutable> | undefined => {
- return state.listView.currentListResourceState;
+ return state.listView.listResourceState;
};
export const getLastLoadedListResourceState = (
state: Immutable
): Immutable> | undefined => {
- return getLastLoadedResourceState(state.listView.currentListResourceState);
+ return getLastLoadedResourceState(state.listView.listResourceState);
};
export const getListItems = (
state: Immutable
): Immutable => {
- return getLastLoadedResourceState(state.listView.currentListResourceState)?.data.items || [];
+ return getLastLoadedResourceState(state.listView.listResourceState)?.data.items || [];
};
-export const getListCurrentPageIndex = (state: Immutable): number => {
- return state.listView.currentPaginationInfo.index;
+export const getCurrentLocationPageIndex = (state: Immutable): number => {
+ return state.location.page_index;
};
-export const getListCurrentPageSize = (state: Immutable): number => {
- return state.listView.currentPaginationInfo.size;
+export const getCurrentLocationPageSize = (state: Immutable): number => {
+ return state.location.page_size;
};
export const getListTotalItemsCount = (state: Immutable): number => {
- return (
- getLastLoadedResourceState(state.listView.currentListResourceState)?.data.totalItemsCount || 0
- );
-};
-
-export const getListCurrentShowValue: (
- state: Immutable
-) => TrustedAppsListPageState['listView']['show'] = (state) => {
- return state.listView.show;
+ return getLastLoadedResourceState(state.listView.listResourceState)?.data.totalItemsCount || 0;
};
-export const getListUrlSearchParams: (
+export const getCurrentLocation = (
state: Immutable
-) => TrustedAppsUrlParams = createSelector(
- getListCurrentPageIndex,
- getListCurrentPageSize,
- getListCurrentShowValue,
- (pageIndex, pageSize, showValue) => {
- return {
- page_index: pageIndex,
- page_size: pageSize,
- show: showValue,
- };
- }
-);
+): TrustedAppsListPageLocation => state.location;
export const getListErrorMessage = (
state: Immutable
): string | undefined => {
- return getCurrentResourceError(state.listView.currentListResourceState)?.message;
+ return getCurrentResourceError(state.listView.listResourceState)?.message;
};
export const isListLoading = (state: Immutable): boolean => {
- return isLoadingResourceState(state.listView.currentListResourceState);
+ return isLoadingResourceState(state.listView.listResourceState);
};
export const isDeletionDialogOpen = (state: Immutable): boolean => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts
index 020a87f526e52..c23b6ceae7b07 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts
@@ -5,11 +5,12 @@
*/
import { combineReducers, createStore } from 'redux';
-import { ServerApiError } from '../../../../common/types';
import { TrustedApp } from '../../../../../common/endpoint/types';
import { RoutingAction } from '../../../../common/store/routing';
import {
+ MANAGEMENT_DEFAULT_PAGE,
+ MANAGEMENT_DEFAULT_PAGE_SIZE,
MANAGEMENT_STORE_GLOBAL_NAMESPACE,
MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE,
} from '../../../common/constants';
@@ -105,54 +106,22 @@ export const createListComplexLoadingResourceState = (
)
);
-export const createDefaultPaginationInfo = () => ({ index: 0, size: 20 });
-
-export const createDefaultListView = (
- freshDataTimestamp: number
-): TrustedAppsListPageState['listView'] => ({
- currentListResourceState: createUninitialisedResourceState(),
- currentPaginationInfo: createDefaultPaginationInfo(),
- freshDataTimestamp,
- show: undefined,
-});
-
-export const createLoadingListViewWithPagination = (
- freshDataTimestamp: number,
- currentPaginationInfo: PaginationInfo,
- previousState: StaleResourceState = createUninitialisedResourceState()
-): TrustedAppsListPageState['listView'] => ({
- currentListResourceState: { type: 'LoadingResourceState', previousState },
- currentPaginationInfo,
- freshDataTimestamp,
- show: undefined,
+export const createDefaultPaginationInfo = () => ({
+ index: MANAGEMENT_DEFAULT_PAGE,
+ size: MANAGEMENT_DEFAULT_PAGE_SIZE,
});
export const createLoadedListViewWithPagination = (
freshDataTimestamp: number,
paginationInfo: PaginationInfo = createDefaultPaginationInfo(),
- currentPaginationInfo: PaginationInfo = createDefaultPaginationInfo(),
totalItemsCount: number = 200
): TrustedAppsListPageState['listView'] => ({
- currentListResourceState: createListLoadedResourceState(
+ listResourceState: createListLoadedResourceState(
paginationInfo,
totalItemsCount,
freshDataTimestamp
),
- currentPaginationInfo,
- freshDataTimestamp,
- show: undefined,
-});
-
-export const createFailedListViewWithPagination = (
- freshDataTimestamp: number,
- currentPaginationInfo: PaginationInfo,
- error: ServerApiError,
- lastLoadedState?: LoadedResourceState
-): TrustedAppsListPageState['listView'] => ({
- currentListResourceState: { type: 'FailedResourceState', error, lastLoadedState },
- currentPaginationInfo,
freshDataTimestamp,
- show: undefined,
});
export const createUserChangedUrlAction = (path: string, search: string = ''): RoutingAction => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx
index d0c1fb477ea46..ae1f314842aab 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx
@@ -23,8 +23,8 @@ import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants';
import { getTrustedAppsListPath } from '../../../common/routing';
import {
- getListCurrentPageIndex,
- getListCurrentPageSize,
+ getCurrentLocationPageIndex,
+ getCurrentLocationPageSize,
getListErrorMessage,
getListItems,
getListTotalItemsCount,
@@ -149,8 +149,8 @@ const getColumnDefinitions = (context: TrustedAppsListContext): ColumnsList => {
export const TrustedAppsList = memo(() => {
const [detailsMap, setDetailsMap] = useState({});
- const pageIndex = useTrustedAppsSelector(getListCurrentPageIndex);
- const pageSize = useTrustedAppsSelector(getListCurrentPageSize);
+ const pageIndex = useTrustedAppsSelector(getCurrentLocationPageIndex);
+ const pageSize = useTrustedAppsSelector(getCurrentLocationPageSize);
const totalItemCount = useTrustedAppsSelector(getListTotalItemsCount);
const listItems = useTrustedAppsSelector(getListItems);
const dispatch = useDispatch();
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
index 878818d9b77fe..d63cda5b513dc 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx
@@ -15,27 +15,26 @@ import { TrustedAppsNotifications } from './trusted_apps_notifications';
import { CreateTrustedAppFlyout } from './components/create_trusted_app_flyout';
import { getTrustedAppsListPath } from '../../../common/routing';
import { useTrustedAppsSelector } from './hooks';
-import { getListCurrentShowValue, getListUrlSearchParams } from '../store/selectors';
+import { getCurrentLocation } from '../store/selectors';
import { TrustedAppsListPageRouteState } from '../../../../../common/endpoint/types';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
export const TrustedAppsPage = memo(() => {
const history = useHistory();
const { state: routeState } = useLocation();
- const urlParams = useTrustedAppsSelector(getListUrlSearchParams);
- const showAddFlout = useTrustedAppsSelector(getListCurrentShowValue) === 'create';
+ const location = useTrustedAppsSelector(getCurrentLocation);
const handleAddButtonClick = useCallback(() => {
history.push(
getTrustedAppsListPath({
- ...urlParams,
+ ...location,
show: 'create',
})
);
- }, [history, urlParams]);
+ }, [history, location]);
const handleAddFlyoutClose = useCallback(() => {
- const { show, ...paginationParamsOnly } = urlParams;
+ const { show, ...paginationParamsOnly } = location;
history.push(getTrustedAppsListPath(paginationParamsOnly));
- }, [history, urlParams]);
+ }, [history, location]);
const backButton = useMemo(() => {
if (routeState && routeState.onBackButtonNavigateTo) {
@@ -50,7 +49,7 @@ export const TrustedAppsPage = memo(() => {
@@ -82,7 +81,7 @@ export const TrustedAppsPage = memo(() => {
>
- {showAddFlout && (
+ {location.show === 'create' && (
Date: Fri, 2 Oct 2020 10:10:38 -0400
Subject: [PATCH 19/50] [Security Solution][Detections] Enrich shell signals
with fields common to all building blocks (#79130)
* Enrich shell signals with fields common to all building blocks
* PR comments + additional unit test
---
.../signals/build_bulk_body.test.ts | 448 +++++++++++++++++-
.../signals/build_bulk_body.ts | 53 +++
2 files changed, 498 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts
index 2f7dd22c0c78e..75a7de8cd2c44 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts
@@ -11,8 +11,15 @@ import {
sampleIdGuid,
sampleDocWithAncestors,
sampleRuleSO,
+ sampleDocNoSortIdNoVersion,
} from './__mocks__/es_results';
-import { buildBulkBody, buildSignalFromSequence, buildSignalFromEvent } from './build_bulk_body';
+import {
+ buildBulkBody,
+ buildSignalFromSequence,
+ buildSignalFromEvent,
+ objectPairIntersection,
+ objectArrayIntersection,
+} from './build_bulk_body';
import { SignalHit } from './types';
import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock';
@@ -438,13 +445,20 @@ describe('buildBulkBody', () => {
describe('buildSignalFromSequence', () => {
test('builds a basic signal from a sequence of building blocks', () => {
- const blocks = [sampleDocWithAncestors().hits.hits[0], sampleDocWithAncestors().hits.hits[0]];
+ const block1 = sampleDocWithAncestors().hits.hits[0];
+ block1._source.new_key = 'new_key_value';
+ block1._source.new_key2 = 'new_key2_value';
+ const block2 = sampleDocWithAncestors().hits.hits[0];
+ block2._source.new_key = 'new_key_value';
+ const blocks = [block1, block2];
const ruleSO = sampleRuleSO();
const signal = buildSignalFromSequence(blocks, ruleSO);
// Timestamp will potentially always be different so remove it for the test
// @ts-expect-error
delete signal['@timestamp'];
- const expected: Omit = {
+ const expected: Omit & { someKey: string; new_key: string } = {
+ someKey: 'someValue',
+ new_key: 'new_key_value',
event: {
kind: 'signal',
},
@@ -539,6 +553,96 @@ describe('buildSignalFromSequence', () => {
};
expect(signal).toEqual(expected);
});
+
+ test('builds a basic signal if there is no overlap between source events', () => {
+ const block1 = sampleDocNoSortIdNoVersion();
+ const block2 = sampleDocNoSortIdNoVersion();
+ block2._source['@timestamp'] = '2021-05-20T22:28:46+0000';
+ block2._source.someKey = 'someOtherValue';
+ const ruleSO = sampleRuleSO();
+ const signal = buildSignalFromSequence([block1, block2], ruleSO);
+ // Timestamp will potentially always be different so remove it for the test
+ // @ts-expect-error
+ delete signal['@timestamp'];
+ const expected: Omit = {
+ event: {
+ kind: 'signal',
+ },
+ signal: {
+ parents: [
+ {
+ id: sampleIdGuid,
+ type: 'event',
+ index: 'myFakeSignalIndex',
+ depth: 0,
+ },
+ {
+ id: sampleIdGuid,
+ type: 'event',
+ index: 'myFakeSignalIndex',
+ depth: 0,
+ },
+ ],
+ ancestors: [
+ {
+ id: sampleIdGuid,
+ type: 'event',
+ index: 'myFakeSignalIndex',
+ depth: 0,
+ },
+ {
+ id: sampleIdGuid,
+ type: 'event',
+ index: 'myFakeSignalIndex',
+ depth: 0,
+ },
+ ],
+ status: 'open',
+ rule: {
+ actions: [],
+ author: ['Elastic'],
+ building_block_type: 'default',
+ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
+ rule_id: 'rule-1',
+ false_positives: [],
+ max_signals: 10000,
+ risk_score: 50,
+ risk_score_mapping: [],
+ output_index: '.siem-signals',
+ description: 'Detecting root and admin users',
+ from: 'now-6m',
+ immutable: false,
+ index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
+ interval: '5m',
+ language: 'kuery',
+ license: 'Elastic License',
+ name: 'rule-name',
+ query: 'user.name: root or user.name: admin',
+ references: ['http://google.com'],
+ severity: 'high',
+ severity_mapping: [],
+ tags: ['some fake tag 1', 'some fake tag 2'],
+ threat: [],
+ type: 'query',
+ to: 'now',
+ note: '',
+ enabled: true,
+ created_by: 'sample user',
+ updated_by: 'sample user',
+ version: 1,
+ updated_at: ruleSO.updated_at ?? '',
+ created_at: ruleSO.attributes.createdAt,
+ throttle: 'no_actions',
+ exceptions_list: getListArrayMock(),
+ },
+ depth: 1,
+ group: {
+ id: '269c1f5754bff92fb8040283b687258e99b03e8b2ab1262cc20c82442e5de5ea',
+ },
+ },
+ };
+ expect(signal).toEqual(expected);
+ });
});
describe('buildSignalFromEvent', () => {
@@ -632,3 +736,341 @@ describe('buildSignalFromEvent', () => {
expect(signal).toEqual(expected);
});
});
+
+describe('recursive intersection between objects', () => {
+ test('should treat numbers and strings as unequal', () => {
+ const a = {
+ field1: 1,
+ field2: 1,
+ };
+ const b = {
+ field1: 1,
+ field2: '1',
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = {
+ field1: 1,
+ };
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should strip unequal numbers and strings', () => {
+ const a = {
+ field1: 1,
+ field2: 1,
+ field3: 'abcd',
+ field4: 'abcd',
+ };
+ const b = {
+ field1: 1,
+ field2: 100,
+ field3: 'abcd',
+ field4: 'wxyz',
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = {
+ field1: 1,
+ field3: 'abcd',
+ };
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should handle null values', () => {
+ const a = {
+ field1: 1,
+ field2: '1',
+ field3: null,
+ };
+ const b = {
+ field1: null,
+ field2: null,
+ field3: null,
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = {
+ field3: null,
+ };
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should handle explicit undefined values and return undefined if left with only undefined fields', () => {
+ const a = {
+ field1: 1,
+ field2: '1',
+ field3: undefined,
+ };
+ const b = {
+ field1: undefined,
+ field2: undefined,
+ field3: undefined,
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = undefined;
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should strip arrays out regardless of whether they are equal', () => {
+ const a = {
+ array_field1: [1, 2],
+ array_field2: [1, 2],
+ };
+ const b = {
+ array_field1: [1, 2],
+ array_field2: [3, 4],
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = undefined;
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should strip fields that are not in both objects', () => {
+ const a = {
+ field1: 1,
+ };
+ const b = {
+ field2: 1,
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = undefined;
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should work on objects within objects', () => {
+ const a = {
+ container_field: {
+ field1: 1,
+ field2: 1,
+ field3: 10,
+ field5: 1,
+ field6: null,
+ array_field: [1, 2],
+ nested_container_field: {
+ field1: 1,
+ field2: 1,
+ },
+ nested_container_field2: {
+ field1: undefined,
+ },
+ },
+ container_field_without_intersection: {
+ sub_field1: 1,
+ },
+ };
+ const b = {
+ container_field: {
+ field1: 1,
+ field2: 2,
+ field4: 10,
+ field5: '1',
+ field6: null,
+ array_field: [1, 2],
+ nested_container_field: {
+ field1: 1,
+ field2: 2,
+ },
+ nested_container_field2: {
+ field1: undefined,
+ },
+ },
+ container_field_without_intersection: {
+ sub_field2: 1,
+ },
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = {
+ container_field: {
+ field1: 1,
+ field6: null,
+ nested_container_field: {
+ field1: 1,
+ },
+ },
+ };
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should work on objects with a variety of fields', () => {
+ const a = {
+ field1: 1,
+ field2: 1,
+ field3: 10,
+ field5: 1,
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 1,
+ sub_field3: 10,
+ },
+ container_field_without_intersection: {
+ sub_field1: 1,
+ },
+ };
+ const b = {
+ field1: 1,
+ field2: 2,
+ field4: 10,
+ field5: '1',
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 2,
+ sub_field4: 10,
+ },
+ container_field_without_intersection: {
+ sub_field2: 1,
+ },
+ };
+ const intersection = objectPairIntersection(a, b);
+ const expected = {
+ field1: 1,
+ field6: null,
+ container_field: {
+ sub_field1: 1,
+ },
+ };
+ expect(intersection).toEqual(expected);
+ });
+});
+
+describe('objectArrayIntersection', () => {
+ test('should return undefined if the array is empty', () => {
+ const intersection = objectArrayIntersection([]);
+ const expected = undefined;
+ expect(intersection).toEqual(expected);
+ });
+ test('should return the initial object if there is only 1', () => {
+ const a = {
+ field1: 1,
+ field2: 1,
+ field3: 10,
+ field5: 1,
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 1,
+ sub_field3: 10,
+ },
+ container_field_without_intersection: {
+ sub_field1: 1,
+ },
+ };
+ const intersection = objectArrayIntersection([a]);
+ const expected = {
+ field1: 1,
+ field2: 1,
+ field3: 10,
+ field5: 1,
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 1,
+ sub_field3: 10,
+ },
+ container_field_without_intersection: {
+ sub_field1: 1,
+ },
+ };
+ expect(intersection).toEqual(expected);
+ });
+ test('should work with exactly 2 objects', () => {
+ const a = {
+ field1: 1,
+ field2: 1,
+ field3: 10,
+ field5: 1,
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 1,
+ sub_field3: 10,
+ },
+ container_field_without_intersection: {
+ sub_field1: 1,
+ },
+ };
+ const b = {
+ field1: 1,
+ field2: 2,
+ field4: 10,
+ field5: '1',
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 2,
+ sub_field4: 10,
+ },
+ container_field_without_intersection: {
+ sub_field2: 1,
+ },
+ };
+ const intersection = objectArrayIntersection([a, b]);
+ const expected = {
+ field1: 1,
+ field6: null,
+ container_field: {
+ sub_field1: 1,
+ },
+ };
+ expect(intersection).toEqual(expected);
+ });
+
+ test('should work with 3 or more objects', () => {
+ const a = {
+ field1: 1,
+ field2: 1,
+ field3: 10,
+ field5: 1,
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 1,
+ sub_field3: 10,
+ },
+ container_field_without_intersection: {
+ sub_field1: 1,
+ },
+ };
+ const b = {
+ field1: 1,
+ field2: 2,
+ field4: 10,
+ field5: '1',
+ field6: null,
+ array_field: [1, 2],
+ container_field: {
+ sub_field1: 1,
+ sub_field2: 2,
+ sub_field4: 10,
+ },
+ container_field_without_intersection: {
+ sub_field2: 1,
+ },
+ };
+ const c = {
+ field1: 1,
+ field2: 2,
+ field4: 10,
+ field5: '1',
+ array_field: [1, 2],
+ container_field: {
+ sub_field2: 2,
+ sub_field4: 10,
+ },
+ container_field_without_intersection: {
+ sub_field2: 1,
+ },
+ };
+ const intersection = objectArrayIntersection([a, b, c]);
+ const expected = {
+ field1: 1,
+ };
+ expect(intersection).toEqual(expected);
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts
index f8632a85c77e9..8e9571fe8a445 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts
@@ -129,7 +129,9 @@ export const buildSignalFromSequence = (
): SignalHit => {
const rule = buildRuleWithoutOverrides(ruleSO);
const signal: Signal = buildSignal(events, rule);
+ const mergedEvents = objectArrayIntersection(events.map((event) => event._source));
return {
+ ...mergedEvents,
'@timestamp': new Date().toISOString(),
event: {
kind: 'signal',
@@ -167,3 +169,54 @@ export const buildSignalFromEvent = (
};
return signalHit;
};
+
+export const objectArrayIntersection = (objects: object[]) => {
+ if (objects.length === 0) {
+ return undefined;
+ } else if (objects.length === 1) {
+ return objects[0];
+ } else {
+ return objects
+ .slice(1)
+ .reduce(
+ (acc: object | undefined, obj): object | undefined => objectPairIntersection(acc, obj),
+ objects[0]
+ );
+ }
+};
+
+export const objectPairIntersection = (a: object | undefined, b: object | undefined) => {
+ if (a === undefined || b === undefined) {
+ return undefined;
+ }
+ const intersection: Record = {};
+ Object.entries(a).forEach(([key, aVal]) => {
+ if (key in b) {
+ const bVal = (b as Record)[key];
+ if (
+ typeof aVal === 'object' &&
+ !(aVal instanceof Array) &&
+ aVal !== null &&
+ typeof bVal === 'object' &&
+ !(bVal instanceof Array) &&
+ bVal !== null
+ ) {
+ intersection[key] = objectPairIntersection(aVal, bVal);
+ } else if (aVal === bVal) {
+ intersection[key] = aVal;
+ }
+ }
+ });
+ // Count up the number of entries that are NOT undefined in the intersection
+ // If there are no keys OR all entries are undefined, return undefined
+ if (
+ Object.values(intersection).reduce(
+ (acc: number, value) => (value !== undefined ? acc + 1 : acc),
+ 0
+ ) === 0
+ ) {
+ return undefined;
+ } else {
+ return intersection;
+ }
+};
From f398b492002dd33c0fc4f764be7a1e6d0d81c6f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Fri, 2 Oct 2020 16:03:42 +0100
Subject: [PATCH 20/50] [Usage Collection] [schema] `actions` (#78832)
---
x-pack/.telemetryrc.json | 1 -
.../server/usage/actions_usage_collector.ts | 23 +++++-
.../schema/xpack_plugins.json | 78 +++++++++++++++++++
3 files changed, 100 insertions(+), 2 deletions(-)
diff --git a/x-pack/.telemetryrc.json b/x-pack/.telemetryrc.json
index c7430666c538f..db50727c599a9 100644
--- a/x-pack/.telemetryrc.json
+++ b/x-pack/.telemetryrc.json
@@ -2,7 +2,6 @@
"output": "plugins/telemetry_collection_xpack/schema/xpack_plugins.json",
"root": "plugins/",
"exclude": [
- "plugins/actions/server/usage/actions_usage_collector.ts",
"plugins/alerts/server/usage/alerts_usage_collector.ts",
"plugins/apm/server/lib/apm_telemetry/index.ts"
]
diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
index aa546e08ea1ba..fac57b6282c44 100644
--- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
+++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
@@ -4,11 +4,26 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { MakeSchemaFrom, UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { get } from 'lodash';
import { TaskManagerStartContract } from '../../../task_manager/server';
import { ActionsUsage } from './types';
+const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
+ // TODO: Find out an automated way to populate the keys or reformat these into an array (and change the Remote Telemetry indexer accordingly)
+ DYNAMIC_KEY: { type: 'long' },
+ // Known actions:
+ __email: { type: 'long' },
+ __index: { type: 'long' },
+ __pagerduty: { type: 'long' },
+ '__server-log': { type: 'long' },
+ __slack: { type: 'long' },
+ __webhook: { type: 'long' },
+ __servicenow: { type: 'long' },
+ __jira: { type: 'long' },
+ __resilient: { type: 'long' },
+};
+
export function createActionsUsageCollector(
usageCollection: UsageCollectionSetup,
taskManager: TaskManagerStartContract
@@ -16,6 +31,12 @@ export function createActionsUsageCollector(
return usageCollection.makeUsageCollector({
type: 'actions',
isReady: () => true,
+ schema: {
+ count_total: { type: 'long' },
+ count_active_total: { type: 'long' },
+ count_by_type: byTypeSchema,
+ count_active_by_type: byTypeSchema,
+ },
fetch: async () => {
try {
const doc = await getLatestTaskState(await taskManager);
diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
index b08585066f100..bdafbfd8ec967 100644
--- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
+++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
@@ -1,5 +1,83 @@
{
"properties": {
+ "actions": {
+ "properties": {
+ "count_total": {
+ "type": "long"
+ },
+ "count_active_total": {
+ "type": "long"
+ },
+ "count_by_type": {
+ "properties": {
+ "DYNAMIC_KEY": {
+ "type": "long"
+ },
+ "__email": {
+ "type": "long"
+ },
+ "__index": {
+ "type": "long"
+ },
+ "__pagerduty": {
+ "type": "long"
+ },
+ "__server-log": {
+ "type": "long"
+ },
+ "__slack": {
+ "type": "long"
+ },
+ "__webhook": {
+ "type": "long"
+ },
+ "__servicenow": {
+ "type": "long"
+ },
+ "__jira": {
+ "type": "long"
+ },
+ "__resilient": {
+ "type": "long"
+ }
+ }
+ },
+ "count_active_by_type": {
+ "properties": {
+ "DYNAMIC_KEY": {
+ "type": "long"
+ },
+ "__email": {
+ "type": "long"
+ },
+ "__index": {
+ "type": "long"
+ },
+ "__pagerduty": {
+ "type": "long"
+ },
+ "__server-log": {
+ "type": "long"
+ },
+ "__slack": {
+ "type": "long"
+ },
+ "__webhook": {
+ "type": "long"
+ },
+ "__servicenow": {
+ "type": "long"
+ },
+ "__jira": {
+ "type": "long"
+ },
+ "__resilient": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
"canvas": {
"properties": {
"workpads": {
From fccfad24cb7856f0a871381524e3e404dedfa839 Mon Sep 17 00:00:00 2001
From: Marta Bondyra
Date: Fri, 2 Oct 2020 17:18:20 +0200
Subject: [PATCH 21/50] [Lens] remove test warnings about improper HTML
structure (#79251)
* [Lens] remove test warnings about improper HTML structure
---
.../workspace_panel/workspace_panel.tsx | 32 ++++++++-----------
1 file changed, 13 insertions(+), 19 deletions(-)
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
index 2a5798ac6a70c..3993b4ffc02b0 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx
@@ -208,27 +208,22 @@ export function InnerWorkspacePanel({
>
- {expression === null ? (
-
- ) : (
-
- )}
+ {expression === null
+ ? i18n.translate('xpack.lens.editorFrame.emptyWorkspace', {
+ defaultMessage: 'Drop some fields here to start',
+ })
+ : i18n.translate('xpack.lens.editorFrame.emptyWorkspaceSimple', {
+ defaultMessage: 'Drop field here',
+ })}
{expression === null && (
<>
-
+ {i18n.translate('xpack.lens.editorFrame.emptyWorkspaceHeading', {
+ defaultMessage: 'Lens is a new tool for creating visualization',
+ })}
@@ -237,10 +232,9 @@ export function InnerWorkspacePanel({
target="_blank"
external
>
-
+ {i18n.translate('xpack.lens.editorFrame.goToForums', {
+ defaultMessage: 'Make requests and give feedback',
+ })}
From 6364c14ffd99fc86cf257beaea81e56e04a41c68 Mon Sep 17 00:00:00 2001
From: Devon Thomson
Date: Fri, 2 Oct 2020 12:31:08 -0400
Subject: [PATCH 22/50] Panel Description Tooltip Design Change (#79213)
* wrapped Embeddable Panel title in EuiTooltip and centered description icon
---
.../public/lib/panel/_embeddable_panel.scss | 5 ++
.../lib/panel/panel_header/panel_header.tsx | 55 ++++++++++---------
2 files changed, 33 insertions(+), 27 deletions(-)
diff --git a/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss b/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss
index 36a7fee14cce1..cdc0f9f0e0451 100644
--- a/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss
+++ b/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss
@@ -54,9 +54,14 @@
.embPanel__titleInner {
overflow: hidden;
display: flex;
+ align-items: center;
padding-right: $euiSizeS;
}
+ .embPanel__titleTooltipAnchor {
+ max-width: 100%;
+ }
+
.embPanel__titleText {
@include euiTextTruncate;
}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx
index c538b98949a43..ea6a6a78c2b67 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx
@@ -99,16 +99,6 @@ function renderNotifications(
});
}
-function renderTooltip(description: string) {
- return (
- description !== '' && (
-
-
-
- )
- );
-}
-
type EmbeddableWithDescription = IEmbeddable & { getDescription: () => string };
function getViewDescription(embeddable: IEmbeddable | EmbeddableWithDescription) {
@@ -134,9 +124,10 @@ export function PanelHeader({
embeddable,
headerId,
}: PanelHeaderProps) {
- const viewDescription = getViewDescription(embeddable);
- const showTitle = !hidePanelTitle && (!isViewMode || title || viewDescription !== '');
- const showPanelBar = !isViewMode || badges.length > 0 || notifications.length > 0 || showTitle;
+ const description = getViewDescription(embeddable);
+ const showTitle = !hidePanelTitle && (!isViewMode || title);
+ const showPanelBar =
+ !isViewMode || badges.length > 0 || notifications.length > 0 || showTitle || description;
const classes = classNames('embPanel__header', {
// eslint-disable-next-line @typescript-eslint/naming-convention
'embPanel__header--floater': !showPanelBar,
@@ -174,26 +165,36 @@ export function PanelHeader({
);
}
+ const renderTitle = () => {
+ const titleComponent = showTitle ? (
+
+ {title || placeholderTitle}
+
+ ) : undefined;
+ return description ? (
+
+
+ {titleComponent}
+
+
+ ) : (
+ titleComponent
+ );
+ };
+
return (
- {showTitle ? (
-
-
- {title || placeholderTitle}
-
- {getAriaLabel()}
- {renderTooltip(viewDescription)}
-
- ) : (
- {getAriaLabel()}
- )}
+ {getAriaLabel()}
+ {renderTitle()}
{renderBadges(badges, embeddable)}
{renderNotifications(notifications, embeddable)}
From d67962453224404647e4859ba6c4f996e64f2e48 Mon Sep 17 00:00:00 2001
From: Marco Liberati
Date: Fri, 2 Oct 2020 18:41:40 +0200
Subject: [PATCH 23/50] [Lens] Fix open custom ranges saved issue (#78915)
Co-authored-by: Elastic Machine
---
.../definitions/ranges/advanced_editor.tsx | 4 +-
.../definitions/ranges/ranges.test.tsx | 42 ++++++++++++++++++-
.../operations/definitions/ranges/ranges.tsx | 19 ++++++---
3 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx
index a6756df403ba7..16b861ae034fa 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx
@@ -132,11 +132,11 @@ export const RangePopover = ({
{
const newRange = {
...tempRange,
- to: target.value !== '' ? Number(target.value) : -Infinity,
+ to: target.value !== '' ? Number(target.value) : Infinity,
};
setTempRange(newRange);
saveRangeAndReset(newRange);
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx
index 2409406afcdbc..fb6cf6df8573f 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx
@@ -485,7 +485,7 @@ describe('ranges', () => {
/>
);
- // This series of act clojures are made to make it work properly the update flush
+ // This series of act closures are made to make it work properly the update flush
act(() => {
instance.find(RangePopover).find(EuiLink).prop('onClick')!({} as ReactMouseEvent);
});
@@ -550,6 +550,46 @@ describe('ranges', () => {
expect(instance.find(RangePopover)).toHaveLength(1);
});
});
+
+ it('should handle correctly open ranges when saved', () => {
+ const setStateSpy = jest.fn();
+
+ // Add an extra open range:
+ (state.layers.first.columns.col1 as RangeIndexPatternColumn).params.ranges.push({
+ from: null,
+ to: null,
+ label: '',
+ });
+
+ const instance = mount(
+
+ );
+
+ act(() => {
+ instance.find(RangePopover).last().find(EuiLink).prop('onClick')!({} as ReactMouseEvent);
+ });
+
+ act(() => {
+ // need another wrapping for this in order to work
+ instance.update();
+
+ // Check UI values for open ranges
+ expect(
+ instance.find(RangePopover).last().find(EuiFieldNumber).first().prop('value')
+ ).toBe('');
+
+ expect(instance.find(RangePopover).last().find(EuiFieldNumber).last().prop('value')).toBe(
+ ''
+ );
+ });
+ });
});
});
});
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx
index a59780ef59939..a8304456262eb 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx
@@ -16,7 +16,13 @@ import { updateColumnParam, changeColumn } from '../../../state_helpers';
import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants';
type RangeType = Omit;
-export type RangeTypeLens = RangeType & { label: string };
+// Try to cover all possible serialized states for ranges
+export type RangeTypeLens = (RangeType | { from: Range['from'] | null; to: Range['to'] | null }) & {
+ label: string;
+};
+
+// This is a subset of RangeTypeLens which has both from and to defined
+type FullRangeTypeLens = Extract>;
export type MODES_TYPES = typeof MODES[keyof typeof MODES];
@@ -35,10 +41,13 @@ export type UpdateParamsFnType = (
value: RangeColumnParams[K]
) => void;
-export const isValidNumber = (value: number | '') =>
- value !== '' && !isNaN(value) && isFinite(value);
-export const isRangeWithin = (range: RangeTypeLens): boolean => range.from <= range.to;
-const isFullRange = ({ from, to }: RangeType) => isValidNumber(from) && isValidNumber(to);
+// on initialization values can be null (from the Infinity serialization), so handle it correctly
+// or they will be casted to 0 by the editor ( see #78867 )
+export const isValidNumber = (value: number | '' | null): value is number =>
+ value != null && value !== '' && !isNaN(value) && isFinite(value);
+export const isRangeWithin = (range: RangeType): boolean => range.from <= range.to;
+const isFullRange = (range: RangeTypeLens): range is FullRangeTypeLens =>
+ isValidNumber(range.from) && isValidNumber(range.to);
export const isValidRange = (range: RangeTypeLens): boolean => {
if (isFullRange(range)) {
return isRangeWithin(range);
From 7afb8b4d7b2813e6235bcb164bbebc4c30f1d43d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Fri, 2 Oct 2020 17:45:47 +0100
Subject: [PATCH 24/50] [Usage Collection] [schema] `alerts` (#78933)
---
x-pack/.telemetryrc.json | 1 -
.../server/usage/alerts_usage_collector.ts | 57 +++++-
.../schema/xpack_plugins.json | 192 ++++++++++++++++++
3 files changed, 248 insertions(+), 2 deletions(-)
diff --git a/x-pack/.telemetryrc.json b/x-pack/.telemetryrc.json
index db50727c599a9..706decfc93e9c 100644
--- a/x-pack/.telemetryrc.json
+++ b/x-pack/.telemetryrc.json
@@ -2,7 +2,6 @@
"output": "plugins/telemetry_collection_xpack/schema/xpack_plugins.json",
"root": "plugins/",
"exclude": [
- "plugins/alerts/server/usage/alerts_usage_collector.ts",
"plugins/apm/server/lib/apm_telemetry/index.ts"
]
}
diff --git a/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts
index 64d3ad54a2318..de82dd31877af 100644
--- a/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts
+++ b/x-pack/plugins/alerts/server/usage/alerts_usage_collector.ts
@@ -4,11 +4,44 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { MakeSchemaFrom, UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { get } from 'lodash';
import { TaskManagerStartContract } from '../../../task_manager/server';
import { AlertsUsage } from './types';
+const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
+ // TODO: Find out an automated way to populate the keys or reformat these into an array (and change the Remote Telemetry indexer accordingly)
+ DYNAMIC_KEY: { type: 'long' },
+ // Known alerts (searching the use of the alerts API `registerType`:
+ // Built-in
+ '__index-threshold': { type: 'long' },
+ // APM
+ apm__error_rate: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ apm__transaction_error_rate: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ apm__transaction_duration: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ apm__transaction_duration_anomaly: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ // Infra
+ metrics__alert__threshold: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ metrics__alert__inventory__threshold: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ logs__alert__document__count: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ // Monitoring
+ monitoring_alert_cluster_health: { type: 'long' },
+ monitoring_alert_cpu_usage: { type: 'long' },
+ monitoring_alert_disk_usage: { type: 'long' },
+ monitoring_alert_elasticsearch_version_mismatch: { type: 'long' },
+ monitoring_alert_kibana_version_mismatch: { type: 'long' },
+ monitoring_alert_license_expiration: { type: 'long' },
+ monitoring_alert_logstash_version_mismatch: { type: 'long' },
+ monitoring_alert_nodes_changed: { type: 'long' },
+ // Security Solution
+ siem__signals: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ siem__notifications: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ // Uptime
+ xpack__uptime__alerts__monitorStatus: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ xpack__uptime__alerts__tls: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ xpack__uptime__alerts__durationAnomaly: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+};
+
export function createAlertsUsageCollector(
usageCollection: UsageCollectionSetup,
taskManager: TaskManagerStartContract
@@ -50,6 +83,28 @@ export function createAlertsUsageCollector(
};
}
},
+ schema: {
+ count_total: { type: 'long' },
+ count_active_total: { type: 'long' },
+ count_disabled_total: { type: 'long' },
+ throttle_time: {
+ min: { type: 'long' },
+ avg: { type: 'float' },
+ max: { type: 'long' },
+ },
+ schedule_time: {
+ min: { type: 'long' },
+ avg: { type: 'float' },
+ max: { type: 'long' },
+ },
+ connectors_per_alert: {
+ min: { type: 'long' },
+ avg: { type: 'float' },
+ max: { type: 'long' },
+ },
+ count_active_by_type: byTypeSchema,
+ count_by_type: byTypeSchema,
+ },
});
}
diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
index bdafbfd8ec967..98230f143d3d6 100644
--- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
+++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
@@ -78,6 +78,198 @@
}
}
},
+ "alerts": {
+ "properties": {
+ "count_total": {
+ "type": "long"
+ },
+ "count_active_total": {
+ "type": "long"
+ },
+ "count_disabled_total": {
+ "type": "long"
+ },
+ "throttle_time": {
+ "properties": {
+ "min": {
+ "type": "long"
+ },
+ "avg": {
+ "type": "float"
+ },
+ "max": {
+ "type": "long"
+ }
+ }
+ },
+ "schedule_time": {
+ "properties": {
+ "min": {
+ "type": "long"
+ },
+ "avg": {
+ "type": "float"
+ },
+ "max": {
+ "type": "long"
+ }
+ }
+ },
+ "connectors_per_alert": {
+ "properties": {
+ "min": {
+ "type": "long"
+ },
+ "avg": {
+ "type": "float"
+ },
+ "max": {
+ "type": "long"
+ }
+ }
+ },
+ "count_active_by_type": {
+ "properties": {
+ "DYNAMIC_KEY": {
+ "type": "long"
+ },
+ "__index-threshold": {
+ "type": "long"
+ },
+ "apm__error_rate": {
+ "type": "long"
+ },
+ "apm__transaction_error_rate": {
+ "type": "long"
+ },
+ "apm__transaction_duration": {
+ "type": "long"
+ },
+ "apm__transaction_duration_anomaly": {
+ "type": "long"
+ },
+ "metrics__alert__threshold": {
+ "type": "long"
+ },
+ "metrics__alert__inventory__threshold": {
+ "type": "long"
+ },
+ "logs__alert__document__count": {
+ "type": "long"
+ },
+ "monitoring_alert_cluster_health": {
+ "type": "long"
+ },
+ "monitoring_alert_cpu_usage": {
+ "type": "long"
+ },
+ "monitoring_alert_disk_usage": {
+ "type": "long"
+ },
+ "monitoring_alert_elasticsearch_version_mismatch": {
+ "type": "long"
+ },
+ "monitoring_alert_kibana_version_mismatch": {
+ "type": "long"
+ },
+ "monitoring_alert_license_expiration": {
+ "type": "long"
+ },
+ "monitoring_alert_logstash_version_mismatch": {
+ "type": "long"
+ },
+ "monitoring_alert_nodes_changed": {
+ "type": "long"
+ },
+ "siem__signals": {
+ "type": "long"
+ },
+ "siem__notifications": {
+ "type": "long"
+ },
+ "xpack__uptime__alerts__monitorStatus": {
+ "type": "long"
+ },
+ "xpack__uptime__alerts__tls": {
+ "type": "long"
+ },
+ "xpack__uptime__alerts__durationAnomaly": {
+ "type": "long"
+ }
+ }
+ },
+ "count_by_type": {
+ "properties": {
+ "DYNAMIC_KEY": {
+ "type": "long"
+ },
+ "__index-threshold": {
+ "type": "long"
+ },
+ "apm__error_rate": {
+ "type": "long"
+ },
+ "apm__transaction_error_rate": {
+ "type": "long"
+ },
+ "apm__transaction_duration": {
+ "type": "long"
+ },
+ "apm__transaction_duration_anomaly": {
+ "type": "long"
+ },
+ "metrics__alert__threshold": {
+ "type": "long"
+ },
+ "metrics__alert__inventory__threshold": {
+ "type": "long"
+ },
+ "logs__alert__document__count": {
+ "type": "long"
+ },
+ "monitoring_alert_cluster_health": {
+ "type": "long"
+ },
+ "monitoring_alert_cpu_usage": {
+ "type": "long"
+ },
+ "monitoring_alert_disk_usage": {
+ "type": "long"
+ },
+ "monitoring_alert_elasticsearch_version_mismatch": {
+ "type": "long"
+ },
+ "monitoring_alert_kibana_version_mismatch": {
+ "type": "long"
+ },
+ "monitoring_alert_license_expiration": {
+ "type": "long"
+ },
+ "monitoring_alert_logstash_version_mismatch": {
+ "type": "long"
+ },
+ "monitoring_alert_nodes_changed": {
+ "type": "long"
+ },
+ "siem__signals": {
+ "type": "long"
+ },
+ "siem__notifications": {
+ "type": "long"
+ },
+ "xpack__uptime__alerts__monitorStatus": {
+ "type": "long"
+ },
+ "xpack__uptime__alerts__tls": {
+ "type": "long"
+ },
+ "xpack__uptime__alerts__durationAnomaly": {
+ "type": "long"
+ }
+ }
+ }
+ }
+ },
"canvas": {
"properties": {
"workpads": {
From d9915fdee08e179a11c494b852faa72ead77f33b Mon Sep 17 00:00:00 2001
From: Ryan Keairns
Date: Fri, 2 Oct 2020 11:47:20 -0500
Subject: [PATCH 25/50] Re-style and re-order top menu buttons (#79206)
* Re-style and re-order top menu buttons
* Update snapshot due to removed fill prop
* Fix link order for Maps
---
.../application/top_nav/get_top_nav_config.ts | 12 +-
.../top_nav_menu_item.test.tsx.snap | 1 -
.../public/top_nav_menu/_index.scss | 4 +
.../public/top_nav_menu/top_nav_menu_item.tsx | 2 +-
.../application/utils/get_top_nav_config.tsx | 185 +++++++++---------
.../lens/public/app_plugin/lens_top_nav.tsx | 36 ++--
.../routes/maps_app/top_nav_config.tsx | 125 ++++++------
7 files changed, 187 insertions(+), 178 deletions(-)
diff --git a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts
index dbdadeb4e4e7c..77c4a2235d471 100644
--- a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts
+++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts
@@ -48,12 +48,12 @@ export function getTopNavConfig(
];
case ViewMode.EDIT:
return [
- getCreateNewConfig(actions[TopNavIds.VISUALIZE]),
- getSaveConfig(actions[TopNavIds.SAVE]),
- getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]),
- getAddConfig(actions[TopNavIds.ADD_EXISTING]),
getOptionsConfig(actions[TopNavIds.OPTIONS]),
getShareConfig(actions[TopNavIds.SHARE]),
+ getAddConfig(actions[TopNavIds.ADD_EXISTING]),
+ getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]),
+ getSaveConfig(actions[TopNavIds.SAVE]),
+ getCreateNewConfig(actions[TopNavIds.VISUALIZE]),
];
default:
return [];
@@ -79,7 +79,9 @@ function getFullScreenConfig(action: NavAction) {
*/
function getEditConfig(action: NavAction) {
return {
+ emphasize: true,
id: 'edit',
+ iconType: 'pencil',
label: i18n.translate('dashboard.topNave.editButtonAriaLabel', {
defaultMessage: 'edit',
}),
@@ -168,7 +170,7 @@ function getAddConfig(action: NavAction) {
function getCreateNewConfig(action: NavAction) {
return {
emphasize: true,
- iconType: 'plusInCircle',
+ iconType: 'plusInCircleFilled',
id: 'addNew',
label: i18n.translate('dashboard.topNave.addNewButtonAriaLabel', {
defaultMessage: 'Create new',
diff --git a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap
index 570699aa0c0e2..155377e5ea335 100644
--- a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap
+++ b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap
@@ -2,7 +2,6 @@
exports[`TopNavMenu Should render emphasized item which should be clickable 1`] = `
* > * {
// TEMP fix to adjust spacing between EuiHeaderList__list items
margin: 0 $euiSizeXS;
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
index 96a205b737273..e503ebb839f48 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
@@ -48,7 +48,7 @@ export function TopNavMenuItem(props: TopNavMenuData) {
};
const btn = props.emphasize ? (
-
+
{upperFirst(props.label || props.id!)}
) : (
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 12720f3f22e7c..cb68a647cb81d 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
@@ -175,54 +175,61 @@ export const getTopNavConfig = (
};
const topNavMenu: TopNavMenuData[] = [
- ...(originatingApp && ((savedVis && savedVis.id) || embeddableId)
- ? [
- {
- id: 'saveAndReturn',
- label: i18n.translate('visualize.topNavMenu.saveAndReturnVisualizationButtonLabel', {
- defaultMessage: 'Save and return',
- }),
- emphasize: true,
- iconType: 'check',
- description: i18n.translate(
- 'visualize.topNavMenu.saveAndReturnVisualizationButtonAriaLabel',
- {
- defaultMessage: 'Finish editing visualization and return to the last app',
- }
- ),
- testId: 'visualizesaveAndReturnButton',
- disableButton: hasUnappliedChanges,
- tooltip() {
- if (hasUnappliedChanges) {
- return i18n.translate(
- 'visualize.topNavMenu.saveAndReturnVisualizationDisabledButtonTooltip',
- {
- defaultMessage: 'Apply or Discard your changes before finishing',
- }
- );
- }
- },
- run: async () => {
- const saveOptions = {
- confirmOverwrite: false,
- returnToOrigin: true,
- };
- if (
- originatingApp === 'dashboards' &&
- dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables &&
- !savedVis
- ) {
- return createVisReference();
- }
- return doSave(saveOptions);
+ {
+ id: 'inspector',
+ label: i18n.translate('visualize.topNavMenu.openInspectorButtonLabel', {
+ defaultMessage: 'inspect',
+ }),
+ description: i18n.translate('visualize.topNavMenu.openInspectorButtonAriaLabel', {
+ defaultMessage: 'Open Inspector for visualization',
+ }),
+ testId: 'openInspectorButton',
+ disableButton() {
+ return !embeddableHandler.hasInspector || !embeddableHandler.hasInspector();
+ },
+ run: openInspector,
+ tooltip() {
+ if (!embeddableHandler.hasInspector || !embeddableHandler.hasInspector()) {
+ return i18n.translate('visualize.topNavMenu.openInspectorDisabledButtonTooltip', {
+ defaultMessage: `This visualization doesn't support any inspectors.`,
+ });
+ }
+ },
+ },
+ {
+ id: 'share',
+ label: i18n.translate('visualize.topNavMenu.shareVisualizationButtonLabel', {
+ defaultMessage: 'share',
+ }),
+ description: i18n.translate('visualize.topNavMenu.shareVisualizationButtonAriaLabel', {
+ defaultMessage: 'Share Visualization',
+ }),
+ testId: 'shareTopNavButton',
+ run: (anchorElement) => {
+ if (share && !embeddableId) {
+ // TODO: support sharing in by-value mode
+ share.toggleShareContextMenu({
+ anchorElement,
+ allowEmbed: true,
+ allowShortUrl: visualizeCapabilities.createShortUrl,
+ shareableUrl: unhashUrl(window.location.href),
+ objectId: savedVis?.id,
+ objectType: 'visualization',
+ sharingData: {
+ title: savedVis?.title,
},
- },
- ]
- : []),
+ isDirty: hasUnappliedChanges || hasUnsavedChanges,
+ });
+ }
+ },
+ // disable the Share button if no action specified
+ disableButton: !share || !!embeddableId,
+ },
...(visualizeCapabilities.save && !embeddableId
? [
{
id: 'save',
+ iconType: savedVis?.id && originatingApp ? undefined : 'save',
label:
savedVis?.id && originatingApp
? i18n.translate('visualize.topNavMenu.saveVisualizationAsButtonLabel', {
@@ -303,56 +310,50 @@ export const getTopNavConfig = (
},
]
: []),
- {
- id: 'share',
- label: i18n.translate('visualize.topNavMenu.shareVisualizationButtonLabel', {
- defaultMessage: 'share',
- }),
- description: i18n.translate('visualize.topNavMenu.shareVisualizationButtonAriaLabel', {
- defaultMessage: 'Share Visualization',
- }),
- testId: 'shareTopNavButton',
- run: (anchorElement) => {
- if (share && !embeddableId) {
- // TODO: support sharing in by-value mode
- share.toggleShareContextMenu({
- anchorElement,
- allowEmbed: true,
- allowShortUrl: visualizeCapabilities.createShortUrl,
- shareableUrl: unhashUrl(window.location.href),
- objectId: savedVis?.id,
- objectType: 'visualization',
- sharingData: {
- title: savedVis?.title,
+ ...(originatingApp && ((savedVis && savedVis.id) || embeddableId)
+ ? [
+ {
+ id: 'saveAndReturn',
+ label: i18n.translate('visualize.topNavMenu.saveAndReturnVisualizationButtonLabel', {
+ defaultMessage: 'Save and return',
+ }),
+ emphasize: true,
+ iconType: 'checkInCircleFilled',
+ description: i18n.translate(
+ 'visualize.topNavMenu.saveAndReturnVisualizationButtonAriaLabel',
+ {
+ defaultMessage: 'Finish editing visualization and return to the last app',
+ }
+ ),
+ testId: 'visualizesaveAndReturnButton',
+ disableButton: hasUnappliedChanges,
+ tooltip() {
+ if (hasUnappliedChanges) {
+ return i18n.translate(
+ 'visualize.topNavMenu.saveAndReturnVisualizationDisabledButtonTooltip',
+ {
+ defaultMessage: 'Apply or Discard your changes before finishing',
+ }
+ );
+ }
},
- isDirty: hasUnappliedChanges || hasUnsavedChanges,
- });
- }
- },
- // disable the Share button if no action specified
- disableButton: !share || !!embeddableId,
- },
- {
- id: 'inspector',
- label: i18n.translate('visualize.topNavMenu.openInspectorButtonLabel', {
- defaultMessage: 'inspect',
- }),
- description: i18n.translate('visualize.topNavMenu.openInspectorButtonAriaLabel', {
- defaultMessage: 'Open Inspector for visualization',
- }),
- testId: 'openInspectorButton',
- disableButton() {
- return !embeddableHandler.hasInspector || !embeddableHandler.hasInspector();
- },
- run: openInspector,
- tooltip() {
- if (!embeddableHandler.hasInspector || !embeddableHandler.hasInspector()) {
- return i18n.translate('visualize.topNavMenu.openInspectorDisabledButtonTooltip', {
- defaultMessage: `This visualization doesn't support any inspectors.`,
- });
- }
- },
- },
+ run: async () => {
+ const saveOptions = {
+ confirmOverwrite: false,
+ returnToOrigin: true,
+ };
+ if (
+ originatingApp === 'dashboards' &&
+ dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables &&
+ !savedVis
+ ) {
+ return createVisReference();
+ }
+ return doSave(saveOptions);
+ },
+ },
+ ]
+ : []),
];
return topNavMenu;
diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
index f6234d063d8cd..9162af52052ee 100644
--- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
@@ -30,24 +30,22 @@ export function getLensTopNavConfig(options: {
defaultMessage: 'Save',
});
- if (showSaveAndReturn) {
+ if (showCancel) {
topNavMenu.push({
- label: i18n.translate('xpack.lens.app.saveAndReturn', {
- defaultMessage: 'Save and return',
+ label: i18n.translate('xpack.lens.app.cancel', {
+ defaultMessage: 'cancel',
}),
- emphasize: true,
- iconType: 'check',
- run: actions.saveAndReturn,
- testId: 'lnsApp_saveAndReturnButton',
- disableButton: !savingPermitted,
- description: i18n.translate('xpack.lens.app.saveAndReturnButtonAriaLabel', {
- defaultMessage: 'Save the current lens visualization and return to the last app',
+ run: actions.cancel,
+ testId: 'lnsApp_cancelButton',
+ description: i18n.translate('xpack.lens.app.cancelButtonAriaLabel', {
+ defaultMessage: 'Return to the last app without saving changes',
}),
});
}
topNavMenu.push({
label: saveButtonLabel,
+ iconType: !showSaveAndReturn ? 'save' : undefined,
emphasize: !showSaveAndReturn,
run: actions.showSaveModal,
testId: 'lnsApp_saveButton',
@@ -57,17 +55,21 @@ export function getLensTopNavConfig(options: {
disableButton: !savingPermitted,
});
- if (showCancel) {
+ if (showSaveAndReturn) {
topNavMenu.push({
- label: i18n.translate('xpack.lens.app.cancel', {
- defaultMessage: 'cancel',
+ label: i18n.translate('xpack.lens.app.saveAndReturn', {
+ defaultMessage: 'Save and return',
}),
- run: actions.cancel,
- testId: 'lnsApp_cancelButton',
- description: i18n.translate('xpack.lens.app.cancelButtonAriaLabel', {
- defaultMessage: 'Return to the last app without saving changes',
+ emphasize: true,
+ iconType: 'checkInCircleFilled',
+ run: actions.saveAndReturn,
+ testId: 'lnsApp_saveAndReturnButton',
+ disableButton: !savingPermitted,
+ description: i18n.translate('xpack.lens.app.saveAndReturnButtonAriaLabel', {
+ defaultMessage: 'Save the current lens visualization and return to the last app',
}),
});
}
+
return topNavMenu;
}
diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx
index 8a0eb8db4d7aa..917abebfb6b25 100644
--- a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx
+++ b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx
@@ -123,31 +123,56 @@ export function getTopNavConfig({
return { id: savedObjectId };
}
- if (hasSaveAndReturnConfig) {
- topNavConfigs.push({
- id: 'saveAndReturn',
- label: i18n.translate('xpack.maps.topNav.saveAndReturnButtonLabel', {
- defaultMessage: 'Save and return',
+ topNavConfigs.push(
+ {
+ id: 'mapSettings',
+ label: i18n.translate('xpack.maps.topNav.openSettingsButtonLabel', {
+ defaultMessage: `Map settings`,
}),
- emphasize: true,
- iconType: 'check',
- run: () => {
- onSave({
- newTitle: savedMap.title ? savedMap.title : '',
- newDescription: savedMap.description ? savedMap.description : '',
- newCopyOnSave: false,
- isTitleDuplicateConfirmed: false,
- returnToOrigin: true,
- onTitleDuplicate: () => {},
- });
+ description: i18n.translate('xpack.maps.topNav.openSettingsDescription', {
+ defaultMessage: `Open map settings`,
+ }),
+ testId: 'openSettingsButton',
+ disableButton() {
+ return isOpenSettingsDisabled;
},
- testId: 'mapSaveAndReturnButton',
- });
- }
+ run() {
+ openMapSettings();
+ },
+ },
+ {
+ id: 'inspect',
+ label: i18n.translate('xpack.maps.topNav.openInspectorButtonLabel', {
+ defaultMessage: `inspect`,
+ }),
+ description: i18n.translate('xpack.maps.topNav.openInspectorDescription', {
+ defaultMessage: `Open Inspector`,
+ }),
+ testId: 'openInspectorButton',
+ run() {
+ getInspector().open(inspectorAdapters, {});
+ },
+ },
+ {
+ id: 'full-screen',
+ label: i18n.translate('xpack.maps.topNav.fullScreenButtonLabel', {
+ defaultMessage: `full screen`,
+ }),
+ description: i18n.translate('xpack.maps.topNav.fullScreenDescription', {
+ defaultMessage: `full screen`,
+ }),
+ testId: 'mapsFullScreenMode',
+ run() {
+ getCoreChrome().setIsVisible(false);
+ enableFullScreen();
+ },
+ }
+ );
if (hasWritePermissions) {
topNavConfigs.push({
id: 'save',
+ iconType: hasSaveAndReturnConfig ? undefined : 'save',
label: hasSaveAndReturnConfig
? i18n.translate('xpack.maps.topNav.saveAsButtonLabel', {
defaultMessage: 'Save as',
@@ -192,51 +217,27 @@ export function getTopNavConfig({
});
}
- topNavConfigs.push(
- {
- id: 'mapSettings',
- label: i18n.translate('xpack.maps.topNav.openSettingsButtonLabel', {
- defaultMessage: `Map settings`,
- }),
- description: i18n.translate('xpack.maps.topNav.openSettingsDescription', {
- defaultMessage: `Open map settings`,
- }),
- testId: 'openSettingsButton',
- disableButton() {
- return isOpenSettingsDisabled;
- },
- run() {
- openMapSettings();
- },
- },
- {
- id: 'inspect',
- label: i18n.translate('xpack.maps.topNav.openInspectorButtonLabel', {
- defaultMessage: `inspect`,
- }),
- description: i18n.translate('xpack.maps.topNav.openInspectorDescription', {
- defaultMessage: `Open Inspector`,
- }),
- testId: 'openInspectorButton',
- run() {
- getInspector().open(inspectorAdapters, {});
- },
- },
- {
- id: 'full-screen',
- label: i18n.translate('xpack.maps.topNav.fullScreenButtonLabel', {
- defaultMessage: `full screen`,
- }),
- description: i18n.translate('xpack.maps.topNav.fullScreenDescription', {
- defaultMessage: `full screen`,
+ if (hasSaveAndReturnConfig) {
+ topNavConfigs.push({
+ id: 'saveAndReturn',
+ label: i18n.translate('xpack.maps.topNav.saveAndReturnButtonLabel', {
+ defaultMessage: 'Save and return',
}),
- testId: 'mapsFullScreenMode',
- run() {
- getCoreChrome().setIsVisible(false);
- enableFullScreen();
+ emphasize: true,
+ iconType: 'checkInCircleFilled',
+ run: () => {
+ onSave({
+ newTitle: savedMap.title ? savedMap.title : '',
+ newDescription: savedMap.description ? savedMap.description : '',
+ newCopyOnSave: false,
+ isTitleDuplicateConfirmed: false,
+ returnToOrigin: true,
+ onTitleDuplicate: () => {},
+ });
},
- }
- );
+ testId: 'mapSaveAndReturnButton',
+ });
+ }
return topNavConfigs;
}
From bb4ad196ea25858234176248030814144a087f0b Mon Sep 17 00:00:00 2001
From: Spencer
Date: Fri, 2 Oct 2020 09:50:49 -0700
Subject: [PATCH 26/50] normalize paths before printing them into the generated
plugin list (#79232)
Co-authored-by: spalger
---
docs/developer/plugin-list.asciidoc | 12 ++++++------
.../src/plugin_list/discover_plugins.ts | 2 +-
.../src/plugin_list/generate_plugin_list.ts | 10 ++++++----
3 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index bf11f87b96ce9..67b7aa8e6a011 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -16,7 +16,7 @@ NOTE:
[discrete]
=== src/plugins
-[%header,cols=2*]
+[%header,cols=2*]
|===
|Name
|Description
@@ -259,7 +259,7 @@ which will load the visualization's editor.
[discrete]
=== x-pack/plugins
-[%header,cols=2*]
+[%header,cols=2*]
|===
|Name
|Description
@@ -515,6 +515,10 @@ As a developer you can reuse and extend built-in alerts and actions UI functiona
in their infrastructure.
+|{kib-repo}blob/{branch}/x-pack/plugins/drilldowns/url_drilldown/README.md[urlDrilldown]
+|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin.
+
+
|{kib-repo}blob/{branch}/x-pack/plugins/watcher/README.md[watcher]
|This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation):
@@ -523,10 +527,6 @@ in their infrastructure.
|Contains HTTP endpoints and UiSettings that are slated for removal.
-|{kib-repo}blob/{branch}/x-pack/plugins/drilldowns/url_drilldown/README.md[urlDrilldown]
-|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin.
-
-
|===
include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1]
diff --git a/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts b/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts
index 5d92ddb600aa9..e8f6735205b19 100644
--- a/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts
+++ b/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts
@@ -29,7 +29,7 @@ import { extractAsciidocInfo } from './extract_asciidoc_info';
export interface Plugin {
id: string;
- relativeDir?: string;
+ relativeDir: string;
relativeReadmePath?: string;
readmeSnippet?: string;
readmeAsciidocAnchor?: string;
diff --git a/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts b/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts
index e1a1323553113..680c220adb18c 100644
--- a/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts
+++ b/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts
@@ -24,9 +24,11 @@ import { REPO_ROOT } from '@kbn/utils';
import { Plugins } from './discover_plugins';
+const sortPlugins = (plugins: Plugins) => plugins.sort((a, b) => a.id.localeCompare(b.id));
+
function* printPlugins(plugins: Plugins, includes: string[]) {
- for (const plugin of plugins) {
- const path = plugin.relativeReadmePath || plugin.relativeDir;
+ for (const plugin of sortPlugins(plugins)) {
+ const path = normalizePath(plugin.relativeReadmePath || plugin.relativeDir);
yield '';
if (plugin.readmeAsciidocAnchor) {
@@ -67,7 +69,7 @@ NOTE:
[discrete]
=== src/plugins
-[%header,cols=2*]
+[%header,cols=2*]
|===
|Name
|Description
@@ -79,7 +81,7 @@ ${Array.from(printPlugins(ossPlugins, includes)).join('\n')}
[discrete]
=== x-pack/plugins
-[%header,cols=2*]
+[%header,cols=2*]
|===
|Name
|Description
From 2899e83df8b849f5fb2a898944c6aada69dff12f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?=
Date: Fri, 2 Oct 2020 18:57:50 +0200
Subject: [PATCH 27/50] [Logs UI] Remove legacy singletons (#77743)
Removes the `npStart` legacy singleton used during the migration to the new platform. The singleton was used in API calls to access the `http.fetch` service. To remove the singleton we have injected `fetch` as a dependency in all functions.
---
.../common/components/get_alert_preview.ts | 4 +-
.../logs/log_analysis/api/ml_cleanup.ts | 56 ++++++-------
.../api/ml_get_jobs_summary_api.ts | 26 +++---
.../logs/log_analysis/api/ml_get_module.ts | 16 ++--
.../log_analysis/api/ml_setup_module_api.ts | 52 +++++++-----
.../log_analysis/api/validate_datasets.ts | 19 +++--
.../logs/log_analysis/api/validate_indices.ts | 26 +++---
.../log_analysis_capabilities.tsx | 15 ++--
.../log_analysis/log_analysis_cleanup.tsx | 21 ++---
.../logs/log_analysis/log_analysis_module.tsx | 28 +++++--
.../log_analysis_module_definition.tsx | 4 +-
.../log_analysis/log_analysis_module_types.ts | 24 ++++--
.../log_analysis/log_analysis_setup_state.ts | 8 +-
.../log_entry_categories/module_descriptor.ts | 80 ++++++++++++-------
.../log_entry_rate/module_descriptor.ts | 72 ++++++++++-------
.../logs/log_entries/api/fetch_log_entries.ts | 13 ++-
.../log_entries/api/fetch_log_entries_item.ts | 19 ++---
.../containers/logs/log_entries/index.ts | 6 +-
.../public/containers/logs/log_flyout.tsx | 4 +-
.../api/fetch_log_entries_highlights.ts | 19 ++---
.../api/fetch_log_summary_highlights.ts | 18 ++---
.../log_highlights/log_entry_highlights.tsx | 23 +++---
.../log_highlights/log_summary_highlights.ts | 21 +++--
.../api/fetch_log_source_configuration.ts | 7 +-
.../log_source/api/fetch_log_source_status.ts | 4 +-
.../api/patch_log_source_configuration.ts | 4 +-
.../containers/logs/log_source/log_source.ts | 10 +--
.../containers/logs/log_stream/index.ts | 19 +++--
.../logs/log_summary/api/fetch_log_summary.ts | 19 ++---
.../logs/log_summary/log_summary.test.tsx | 24 ++++--
.../logs/log_summary/log_summary.tsx | 19 +++--
.../public/containers/ml/api/ml_cleanup.ts | 57 ++++++-------
.../ml/api/ml_get_jobs_summary_api.ts | 26 +++---
.../public/containers/ml/api/ml_get_module.ts | 16 ++--
.../containers/ml/api/ml_setup_module_api.ts | 52 +++++++-----
.../containers/ml/infra_ml_capabilities.tsx | 5 +-
.../public/containers/ml/infra_ml_cleanup.tsx | 20 +++--
.../public/containers/ml/infra_ml_module.tsx | 31 ++++---
.../ml/infra_ml_module_definition.tsx | 4 +-
.../containers/ml/infra_ml_module_types.ts | 37 ++++++---
.../metrics_hosts/module_descriptor.ts | 53 ++++++------
.../modules/metrics_k8s/module_descriptor.ts | 53 ++++++------
.../plugins/infra/public/legacy_singletons.ts | 14 ----
.../pages/link_to/link_to_logs.test.tsx | 1 -
.../get_log_entry_category_datasets.ts | 27 ++++---
.../get_log_entry_category_examples.ts | 31 +++----
.../get_top_log_entry_categories.ts | 30 +++----
.../use_log_entry_categories_results.ts | 20 +++--
.../use_log_entry_category_examples.tsx | 16 ++--
.../service_calls/get_log_entry_anomalies.ts | 23 +++---
.../get_log_entry_anomalies_datasets.ts | 16 ++--
.../service_calls/get_log_entry_examples.ts | 33 ++++----
.../service_calls/get_log_entry_rate.ts | 31 ++++---
.../use_log_entry_anomalies_results.ts | 25 ++++--
.../log_entry_rate/use_log_entry_examples.ts | 17 ++--
.../use_log_entry_rate_results.ts | 15 ++--
.../hooks/use_metrics_hosts_anomalies.ts | 43 ++++++----
.../hooks/use_metrics_k8s_anomalies.ts | 45 +++++++----
x-pack/plugins/infra/public/plugin.ts | 5 +-
59 files changed, 798 insertions(+), 628 deletions(-)
delete mode 100644 x-pack/plugins/infra/public/legacy_singletons.ts
diff --git a/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts b/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts
index 207d8a722a8c6..ea50ea6f11f3a 100644
--- a/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts
+++ b/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { HttpSetup } from 'src/core/public';
+import type { HttpHandler } from 'src/core/public';
import {
INFRA_ALERT_PREVIEW_PATH,
METRIC_THRESHOLD_ALERT_TYPE_ID,
@@ -22,7 +22,7 @@ export async function getAlertPreview({
params,
alertType,
}: {
- fetch: HttpSetup['fetch'];
+ fetch: HttpHandler;
params: AlertPreviewRequestParams;
alertType: PreviewableAlertTypes;
}): Promise {
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts
index 6fa2ac175ace6..4fdd6bdd282ba 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts
@@ -5,21 +5,25 @@
*/
import * as rt from 'io-ts';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { getDatafeedId, getJobId } from '../../../../../common/log_analysis';
-import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+
+interface DeleteJobsRequestArgs {
+ spaceId: string;
+ sourceId: string;
+ jobTypes: JobType[];
+}
export const callDeleteJobs = async (
- spaceId: string,
- sourceId: string,
- jobTypes: JobType[]
+ requestArgs: DeleteJobsRequestArgs,
+ fetch: HttpHandler
) => {
+ const { spaceId, sourceId, jobTypes } = requestArgs;
+
// NOTE: Deleting the jobs via this API will delete the datafeeds at the same time
- const deleteJobsResponse = await npStart.http.fetch('/api/ml/jobs/delete_jobs', {
+ const deleteJobsResponse = await fetch('/api/ml/jobs/delete_jobs', {
method: 'POST',
body: JSON.stringify(
deleteJobsRequestPayloadRT.encode({
@@ -28,28 +32,29 @@ export const callDeleteJobs = async (
),
});
- return pipe(
- deleteJobsResponsePayloadRT.decode(deleteJobsResponse),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(deleteJobsResponsePayloadRT)(deleteJobsResponse);
};
-export const callGetJobDeletionTasks = async () => {
- const jobDeletionTasksResponse = await npStart.http.fetch('/api/ml/jobs/deleting_jobs_tasks');
+export const callGetJobDeletionTasks = async (fetch: HttpHandler) => {
+ const jobDeletionTasksResponse = await fetch('/api/ml/jobs/deleting_jobs_tasks');
- return pipe(
- getJobDeletionTasksResponsePayloadRT.decode(jobDeletionTasksResponse),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getJobDeletionTasksResponsePayloadRT)(jobDeletionTasksResponse);
};
+interface StopDatafeedsRequestArgs {
+ spaceId: string;
+ sourceId: string;
+ jobTypes: JobType[];
+}
+
export const callStopDatafeeds = async (
- spaceId: string,
- sourceId: string,
- jobTypes: JobType[]
+ requestArgs: StopDatafeedsRequestArgs,
+ fetch: HttpHandler
) => {
+ const { spaceId, sourceId, jobTypes } = requestArgs;
+
// Stop datafeed due to https://github.com/elastic/kibana/issues/44652
- const stopDatafeedResponse = await npStart.http.fetch('/api/ml/jobs/stop_datafeeds', {
+ const stopDatafeedResponse = await fetch('/api/ml/jobs/stop_datafeeds', {
method: 'POST',
body: JSON.stringify(
stopDatafeedsRequestPayloadRT.encode({
@@ -58,10 +63,7 @@ export const callStopDatafeeds = async (
),
});
- return pipe(
- stopDatafeedsResponsePayloadRT.decode(stopDatafeedResponse),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(stopDatafeedsResponsePayloadRT)(stopDatafeedResponse);
};
export const deleteJobsRequestPayloadRT = rt.type({
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts
index 7441c0ab7d34c..7cb477dbe5b37 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts
@@ -4,21 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { getJobId, jobCustomSettingsRT } from '../../../../../common/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+
+interface RequestArgs {
+ spaceId: string;
+ sourceId: string;
+ jobTypes: JobType[];
+}
export const callJobsSummaryAPI = async (
- spaceId: string,
- sourceId: string,
- jobTypes: JobType[]
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch('/api/ml/jobs/jobs_summary', {
+ const { spaceId, sourceId, jobTypes } = requestArgs;
+ const response = await fetch('/api/ml/jobs/jobs_summary', {
method: 'POST',
body: JSON.stringify(
fetchJobStatusRequestPayloadRT.encode({
@@ -26,10 +29,7 @@ export const callJobsSummaryAPI = async (
})
),
});
- return pipe(
- fetchJobStatusResponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(fetchJobStatusResponsePayloadRT)(response);
};
export const fetchJobStatusRequestPayloadRT = rt.type({
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts
index b6b40d6dc651f..2bf18d4e52c79 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts
@@ -4,24 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { jobCustomSettingsRT } from '../../../../../common/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
-export const callGetMlModuleAPI = async (moduleId: string) => {
- const response = await npStart.http.fetch(`/api/ml/modules/get_module/${moduleId}`, {
+export const callGetMlModuleAPI = async (moduleId: string, fetch: HttpHandler) => {
+ const response = await fetch(`/api/ml/modules/get_module/${moduleId}`, {
method: 'GET',
});
- return pipe(
- getMlModuleResponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getMlModuleResponsePayloadRT)(response);
};
const jobDefinitionRT = rt.type({
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 7c8d63374924c..1f203ef9618b8 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
@@ -4,27 +4,38 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { getJobIdPrefix, jobCustomSettingsRT } from '../../../../../common/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
-
-export const callSetupMlModuleAPI = async (
- moduleId: string,
- start: number | undefined,
- end: number | undefined,
- spaceId: string,
- sourceId: string,
- indexPattern: string,
- jobOverrides: SetupMlModuleJobOverrides[] = [],
- datafeedOverrides: SetupMlModuleDatafeedOverrides[] = [],
- query?: object
-) => {
- const response = await npStart.http.fetch(`/api/ml/modules/setup/${moduleId}`, {
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+
+interface RequestArgs {
+ moduleId: string;
+ start?: number;
+ end?: number;
+ spaceId: string;
+ sourceId: string;
+ indexPattern: string;
+ jobOverrides?: SetupMlModuleJobOverrides[];
+ datafeedOverrides?: SetupMlModuleDatafeedOverrides[];
+ query?: object;
+}
+
+export const callSetupMlModuleAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
+ const {
+ moduleId,
+ start,
+ end,
+ spaceId,
+ sourceId,
+ indexPattern,
+ jobOverrides = [],
+ datafeedOverrides = [],
+ query,
+ } = requestArgs;
+
+ const response = await fetch(`/api/ml/modules/setup/${moduleId}`, {
method: 'POST',
body: JSON.stringify(
setupMlModuleRequestPayloadRT.encode({
@@ -40,10 +51,7 @@ export const callSetupMlModuleAPI = async (
),
});
- return pipe(
- setupMlModuleResponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(setupMlModuleResponsePayloadRT)(response);
};
const setupMlModuleTimeParamsRT = rt.partial({
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_datasets.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_datasets.ts
index 6c9d5e439d359..ec08d3ac107e5 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_datasets.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_datasets.ts
@@ -4,21 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import type { HttpHandler } from 'src/core/public';
import {
LOG_ANALYSIS_VALIDATE_DATASETS_PATH,
validateLogEntryDatasetsRequestPayloadRT,
validateLogEntryDatasetsResponsePayloadRT,
} from '../../../../../common/http_api';
import { decodeOrThrow } from '../../../../../common/runtime_types';
-import { npStart } from '../../../../legacy_singletons';
-export const callValidateDatasetsAPI = async (
- indices: string[],
- timestampField: string,
- startTime: number,
- endTime: number
-) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_VALIDATE_DATASETS_PATH, {
+interface RequestArgs {
+ indices: string[];
+ timestampField: string;
+ startTime: number;
+ endTime: number;
+}
+
+export const callValidateDatasetsAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
+ const { indices, timestampField, startTime, endTime } = requestArgs;
+ const response = await fetch(LOG_ANALYSIS_VALIDATE_DATASETS_PATH, {
method: 'POST',
body: JSON.stringify(
validateLogEntryDatasetsRequestPayloadRT.encode({
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts
index bbef7d201045f..465d09a744b19 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts
@@ -4,10 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
+
import {
LOG_ANALYSIS_VALIDATE_INDICES_PATH,
ValidationIndicesFieldSpecification,
@@ -15,19 +13,19 @@ import {
validationIndicesResponsePayloadRT,
} from '../../../../../common/http_api';
-import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+
+interface RequestArgs {
+ indices: string[];
+ fields: ValidationIndicesFieldSpecification[];
+}
-export const callValidateIndicesAPI = async (
- indices: string[],
- fields: ValidationIndicesFieldSpecification[]
-) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_VALIDATE_INDICES_PATH, {
+export const callValidateIndicesAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
+ const { indices, fields } = requestArgs;
+ const response = await fetch(LOG_ANALYSIS_VALIDATE_INDICES_PATH, {
method: 'POST',
body: JSON.stringify(validationIndicesRequestPayloadRT.encode({ data: { indices, fields } })),
});
- return pipe(
- validationIndicesResponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(validationIndicesResponsePayloadRT)(response);
};
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx
index 9116900ec2196..74b316f78259f 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx
@@ -6,18 +6,16 @@
import createContainer from 'constate';
import { useMemo, useState, useEffect } from 'react';
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
-import { npStart } from '../../../legacy_singletons';
import {
getMlCapabilitiesResponsePayloadRT,
GetMlCapabilitiesResponsePayload,
} from './api/ml_api_types';
-import { throwErrors, createPlainError } from '../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../common/runtime_types';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
export const useLogAnalysisCapabilities = () => {
+ const { services } = useKibanaContextForPlugin();
const [mlCapabilities, setMlCapabilities] = useState(
initialMlCapabilities
);
@@ -26,12 +24,9 @@ export const useLogAnalysisCapabilities = () => {
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
- const rawResponse = await npStart.http.fetch('/api/ml/ml_capabilities');
+ const rawResponse = await services.http.fetch('/api/ml/ml_capabilities');
- return pipe(
- getMlCapabilitiesResponsePayloadRT.decode(rawResponse),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getMlCapabilitiesResponsePayloadRT)(rawResponse);
},
onResolve: (response) => {
setMlCapabilities(response);
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx
index 522616f83d0cb..ec5e879131aa1 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx
@@ -3,17 +3,18 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
+import type { HttpHandler } from 'src/core/public';
import { getJobId } from '../../../../common/log_analysis';
import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './api/ml_cleanup';
export const cleanUpJobsAndDatafeeds = async (
spaceId: string,
sourceId: string,
- jobTypes: JobType[]
+ jobTypes: JobType[],
+ fetch: HttpHandler
) => {
try {
- await callStopDatafeeds(spaceId, sourceId, jobTypes);
+ await callStopDatafeeds({ spaceId, sourceId, jobTypes }, fetch);
} catch (err) {
// Proceed only if datafeed has been deleted or didn't exist in the first place
if (err?.res?.status !== 404) {
@@ -21,27 +22,29 @@ export const cleanUpJobsAndDatafeeds = async (
}
}
- return await deleteJobs(spaceId, sourceId, jobTypes);
+ return await deleteJobs(spaceId, sourceId, jobTypes, fetch);
};
const deleteJobs = async (
spaceId: string,
sourceId: string,
- jobTypes: JobType[]
+ jobTypes: JobType[],
+ fetch: HttpHandler
) => {
- const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId, jobTypes);
- await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes);
+ const deleteJobsResponse = await callDeleteJobs({ spaceId, sourceId, jobTypes }, fetch);
+ await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes, fetch);
return deleteJobsResponse;
};
const waitUntilJobsAreDeleted = async (
spaceId: string,
sourceId: string,
- jobTypes: JobType[]
+ jobTypes: JobType[],
+ fetch: HttpHandler
) => {
const moduleJobIds = jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType));
while (true) {
- const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks();
+ const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks(fetch);
const needToWait = jobIdsBeingDeleted.some((jobId) => moduleJobIds.includes(jobId));
if (needToWait) {
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx
index 79768302a7310..27ef0039ae49f 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx
@@ -6,6 +6,7 @@
import { useCallback, useMemo } from 'react';
import { DatasetFilter } from '../../../../common/log_analysis';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { useModuleStatus } from './log_analysis_module_status';
import { ModuleDescriptor, ModuleSourceConfiguration } from './log_analysis_module_types';
@@ -17,6 +18,7 @@ export const useLogAnalysisModule = ({
sourceConfiguration: ModuleSourceConfiguration;
moduleDescriptor: ModuleDescriptor;
}) => {
+ const { services } = useKibanaContextForPlugin();
const { spaceId, sourceId, timestampField } = sourceConfiguration;
const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes);
@@ -25,7 +27,7 @@ export const useLogAnalysisModule = ({
cancelPreviousOn: 'resolution',
createPromise: async () => {
dispatchModuleStatus({ type: 'fetchingJobStatuses' });
- return await moduleDescriptor.getJobSummary(spaceId, sourceId);
+ return await moduleDescriptor.getJobSummary(spaceId, sourceId, services.http.fetch);
},
onResolve: (jobResponse) => {
dispatchModuleStatus({
@@ -52,13 +54,23 @@ export const useLogAnalysisModule = ({
datasetFilter: DatasetFilter
) => {
dispatchModuleStatus({ type: 'startedSetup' });
- const setupResult = await moduleDescriptor.setUpModule(start, end, datasetFilter, {
- indices: selectedIndices,
- sourceId,
+ const setupResult = await moduleDescriptor.setUpModule(
+ start,
+ end,
+ datasetFilter,
+ {
+ indices: selectedIndices,
+ sourceId,
+ spaceId,
+ timestampField,
+ },
+ services.http.fetch
+ );
+ const jobSummaries = await moduleDescriptor.getJobSummary(
spaceId,
- timestampField,
- });
- const jobSummaries = await moduleDescriptor.getJobSummary(spaceId, sourceId);
+ sourceId,
+ services.http.fetch
+ );
return { setupResult, jobSummaries };
},
onResolve: ({ setupResult: { datafeeds, jobs }, jobSummaries }) => {
@@ -82,7 +94,7 @@ export const useLogAnalysisModule = ({
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
- return await moduleDescriptor.cleanUpModule(spaceId, sourceId);
+ return await moduleDescriptor.cleanUpModule(spaceId, sourceId, services.http.fetch);
},
},
[spaceId, sourceId]
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_definition.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_definition.tsx
index 1f643d0e5eb34..7a5c1d354dc34 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_definition.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_definition.tsx
@@ -6,6 +6,7 @@
import { useCallback, useMemo, useState } from 'react';
import { getJobId } from '../../../../common/log_analysis';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { JobSummary } from './api/ml_get_jobs_summary_api';
import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module';
@@ -18,6 +19,7 @@ export const useLogAnalysisModuleDefinition = ({
sourceConfiguration: ModuleSourceConfiguration;
moduleDescriptor: ModuleDescriptor;
}) => {
+ const { services } = useKibanaContextForPlugin();
const [moduleDefinition, setModuleDefinition] = useState<
GetMlModuleResponsePayload | undefined
>();
@@ -40,7 +42,7 @@ export const useLogAnalysisModuleDefinition = ({
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
- return await moduleDescriptor.getModuleDefinition();
+ return await moduleDescriptor.getModuleDefinition(services.http.fetch);
},
onResolve: (response) => {
setModuleDefinition(response);
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts
index ba355ad195b11..c42704860b032 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import type { HttpHandler } from 'src/core/public';
import {
ValidateLogEntryDatasetsResponsePayload,
ValidationIndicesResponsePayload,
@@ -23,24 +24,35 @@ export interface ModuleDescriptor {
jobTypes: JobType[];
bucketSpan: number;
getJobIds: (spaceId: string, sourceId: string) => Record;
- getJobSummary: (spaceId: string, sourceId: string) => Promise;
- getModuleDefinition: () => Promise;
+ getJobSummary: (
+ spaceId: string,
+ sourceId: string,
+ fetch: HttpHandler
+ ) => Promise;
+ getModuleDefinition: (fetch: HttpHandler) => Promise;
setUpModule: (
start: number | undefined,
end: number | undefined,
datasetFilter: DatasetFilter,
- sourceConfiguration: ModuleSourceConfiguration
+ sourceConfiguration: ModuleSourceConfiguration,
+ fetch: HttpHandler
) => Promise;
- cleanUpModule: (spaceId: string, sourceId: string) => Promise;
+ cleanUpModule: (
+ spaceId: string,
+ sourceId: string,
+ fetch: HttpHandler
+ ) => Promise;
validateSetupIndices: (
indices: string[],
- timestampField: string
+ timestampField: string,
+ fetch: HttpHandler
) => Promise;
validateSetupDatasets: (
indices: string[],
timestampField: string,
startTime: number,
- endTime: number
+ endTime: number,
+ fetch: HttpHandler
) => Promise;
}
diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts
index e6fe8f4e92cc4..750a7104a3a98 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts
@@ -18,6 +18,7 @@ import {
ValidationIndicesError,
ValidationUIError,
} from '../../../components/logging/log_analysis_setup/initial_configuration_step';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { ModuleDescriptor, ModuleSourceConfiguration } from './log_analysis_module_types';
@@ -43,6 +44,7 @@ export const useAnalysisSetupState = ({
setUpModule,
sourceConfiguration,
}: AnalysisSetupStateArguments) => {
+ const { services } = useKibanaContextForPlugin();
const [startTime, setStartTime] = useState(Date.now() - fourWeeksInMs);
const [endTime, setEndTime] = useState(undefined);
@@ -158,7 +160,8 @@ export const useAnalysisSetupState = ({
createPromise: async () => {
return await validateSetupIndices(
sourceConfiguration.indices,
- sourceConfiguration.timestampField
+ sourceConfiguration.timestampField,
+ services.http.fetch
);
},
onResolve: ({ data: { errors } }) => {
@@ -183,7 +186,8 @@ export const useAnalysisSetupState = ({
validIndexNames,
sourceConfiguration.timestampField,
startTime ?? 0,
- endTime ?? Date.now()
+ endTime ?? Date.now(),
+ services.http.fetch
);
},
onResolve: ({ data: { datasets } }) => {
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 9682b3e74db3b..46b28e091cc5c 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
@@ -5,6 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
+import type { HttpHandler } from 'src/core/public';
import {
bucketSpan,
categoriesMessageField,
@@ -42,22 +43,26 @@ const getJobIds = (spaceId: string, sourceId: string) =>
{} as Record
);
-const getJobSummary = async (spaceId: string, sourceId: string) => {
- const response = await callJobsSummaryAPI(spaceId, sourceId, logEntryCategoriesJobTypes);
+const getJobSummary = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ const response = await callJobsSummaryAPI(
+ { spaceId, sourceId, jobTypes: logEntryCategoriesJobTypes },
+ fetch
+ );
const jobIds = Object.values(getJobIds(spaceId, sourceId));
return response.filter((jobSummary) => jobIds.includes(jobSummary.id));
};
-const getModuleDefinition = async () => {
- return await callGetMlModuleAPI(moduleId);
+const getModuleDefinition = async (fetch: HttpHandler) => {
+ return await callGetMlModuleAPI(moduleId, fetch);
};
const setUpModule = async (
start: number | undefined,
end: number | undefined,
datasetFilter: DatasetFilter,
- { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration
+ { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
+ fetch: HttpHandler
) => {
const indexNamePattern = indices.join(',');
const jobOverrides = [
@@ -101,46 +106,59 @@ const setUpModule = async (
};
return callSetupMlModuleAPI(
- moduleId,
- start,
- end,
- spaceId,
- sourceId,
- indexNamePattern,
- jobOverrides,
- [],
- query
+ {
+ moduleId,
+ start,
+ end,
+ spaceId,
+ sourceId,
+ indexPattern: indexNamePattern,
+ jobOverrides,
+ query,
+ },
+ fetch
);
};
-const cleanUpModule = async (spaceId: string, sourceId: string) => {
- return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryCategoriesJobTypes);
+const cleanUpModule = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryCategoriesJobTypes, fetch);
};
-const validateSetupIndices = async (indices: string[], timestampField: string) => {
- return await callValidateIndicesAPI(indices, [
- {
- name: timestampField,
- validTypes: ['date'],
- },
- {
- name: partitionField,
- validTypes: ['keyword'],
- },
+const validateSetupIndices = async (
+ indices: string[],
+ timestampField: string,
+ fetch: HttpHandler
+) => {
+ return await callValidateIndicesAPI(
{
- name: categoriesMessageField,
- validTypes: ['text'],
+ indices,
+ fields: [
+ {
+ name: timestampField,
+ validTypes: ['date'],
+ },
+ {
+ name: partitionField,
+ validTypes: ['keyword'],
+ },
+ {
+ name: categoriesMessageField,
+ validTypes: ['text'],
+ },
+ ],
},
- ]);
+ fetch
+ );
};
const validateSetupDatasets = async (
indices: string[],
timestampField: string,
startTime: number,
- endTime: number
+ endTime: number,
+ fetch: HttpHandler
) => {
- return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime);
+ return await callValidateDatasetsAPI({ indices, timestampField, startTime, endTime }, fetch);
};
export const logEntryCategoriesModule: ModuleDescriptor = {
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 001174a2b7558..b97ec55105f5d 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
@@ -5,6 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
+import type { HttpHandler } from 'src/core/public';
import {
bucketSpan,
DatasetFilter,
@@ -41,22 +42,26 @@ const getJobIds = (spaceId: string, sourceId: string) =>
{} as Record
);
-const getJobSummary = async (spaceId: string, sourceId: string) => {
- const response = await callJobsSummaryAPI(spaceId, sourceId, logEntryRateJobTypes);
+const getJobSummary = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ const response = await callJobsSummaryAPI(
+ { spaceId, sourceId, jobTypes: logEntryRateJobTypes },
+ fetch
+ );
const jobIds = Object.values(getJobIds(spaceId, sourceId));
return response.filter((jobSummary) => jobIds.includes(jobSummary.id));
};
-const getModuleDefinition = async () => {
- return await callGetMlModuleAPI(moduleId);
+const getModuleDefinition = async (fetch: HttpHandler) => {
+ return await callGetMlModuleAPI(moduleId, fetch);
};
const setUpModule = async (
start: number | undefined,
end: number | undefined,
datasetFilter: DatasetFilter,
- { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration
+ { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
+ fetch: HttpHandler
) => {
const indexNamePattern = indices.join(',');
const jobOverrides = [
@@ -93,42 +98,55 @@ const setUpModule = async (
: undefined;
return callSetupMlModuleAPI(
- moduleId,
- start,
- end,
- spaceId,
- sourceId,
- indexNamePattern,
- jobOverrides,
- [],
- query
+ {
+ moduleId,
+ start,
+ end,
+ spaceId,
+ sourceId,
+ indexPattern: indexNamePattern,
+ jobOverrides,
+ query,
+ },
+ fetch
);
};
-const cleanUpModule = async (spaceId: string, sourceId: string) => {
- return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryRateJobTypes);
+const cleanUpModule = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryRateJobTypes, fetch);
};
-const validateSetupIndices = async (indices: string[], timestampField: string) => {
- return await callValidateIndicesAPI(indices, [
- {
- name: timestampField,
- validTypes: ['date'],
- },
+const validateSetupIndices = async (
+ indices: string[],
+ timestampField: string,
+ fetch: HttpHandler
+) => {
+ return await callValidateIndicesAPI(
{
- name: partitionField,
- validTypes: ['keyword'],
+ indices,
+ fields: [
+ {
+ name: timestampField,
+ validTypes: ['date'],
+ },
+ {
+ name: partitionField,
+ validTypes: ['keyword'],
+ },
+ ],
},
- ]);
+ fetch
+ );
};
const validateSetupDatasets = async (
indices: string[],
timestampField: string,
startTime: number,
- endTime: number
+ endTime: number,
+ fetch: HttpHandler
) => {
- return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime);
+ return await callValidateDatasetsAPI({ indices, timestampField, startTime, endTime }, fetch);
};
export const logEntryRateModule: ModuleDescriptor = {
diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries.ts
index 2a19a82892427..3bbd86cb0ef75 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries.ts
@@ -4,12 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
-import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
import {
LOG_ENTRIES_PATH,
@@ -18,11 +15,11 @@ import {
logEntriesResponseRT,
} from '../../../../../common/http_api';
-export const fetchLogEntries = async (requestArgs: LogEntriesRequest) => {
- const response = await npStart.http.fetch(LOG_ENTRIES_PATH, {
+export const fetchLogEntries = async (requestArgs: LogEntriesRequest, fetch: HttpHandler) => {
+ const response = await fetch(LOG_ENTRIES_PATH, {
method: 'POST',
body: JSON.stringify(logEntriesRequestRT.encode(requestArgs)),
});
- return pipe(logEntriesResponseRT.decode(response), fold(throwErrors(createPlainError), identity));
+ return decodeOrThrow(logEntriesResponseRT)(response);
};
diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries_item.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries_item.ts
index 5fde01e458e36..d459fba6cf957 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries_item.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries_item.ts
@@ -4,12 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
-import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
import {
LOG_ENTRIES_ITEM_PATH,
@@ -18,14 +15,14 @@ import {
logEntriesItemResponseRT,
} from '../../../../../common/http_api';
-export const fetchLogEntriesItem = async (requestArgs: LogEntriesItemRequest) => {
- const response = await npStart.http.fetch(LOG_ENTRIES_ITEM_PATH, {
+export const fetchLogEntriesItem = async (
+ requestArgs: LogEntriesItemRequest,
+ fetch: HttpHandler
+) => {
+ const response = await fetch(LOG_ENTRIES_ITEM_PATH, {
method: 'POST',
body: JSON.stringify(logEntriesItemRequestRT.encode(requestArgs)),
});
- return pipe(
- logEntriesItemResponseRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(logEntriesItemResponseRT)(response);
};
diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts
index d5b2a0aaa61c0..4c8c610794b2e 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts
@@ -14,6 +14,7 @@ import {
LogEntriesBaseRequest,
} from '../../../../common/http_api';
import { fetchLogEntries } from './api/fetch_log_entries';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
const DESIRED_BUFFER_PAGES = 2;
const LIVE_STREAM_INTERVAL = 5000;
@@ -144,6 +145,7 @@ const useFetchEntriesEffect = (
dispatch: Dispatch,
props: LogEntriesProps
) => {
+ const { services } = useKibanaContextForPlugin();
const [prevParams, cachePrevParams] = useState();
const [startedStreaming, setStartedStreaming] = useState(false);
@@ -172,7 +174,7 @@ const useFetchEntriesEffect = (
before: 'last',
};
- const { data: payload } = await fetchLogEntries(fetchArgs);
+ const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch);
dispatch({ type: Action.ReceiveNewEntries, payload });
// Move position to the bottom if it's the first load.
@@ -228,7 +230,7 @@ const useFetchEntriesEffect = (
after: state.bottomCursor,
};
- const { data: payload } = await fetchLogEntries(fetchArgs);
+ const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch);
dispatch({
type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter,
diff --git a/x-pack/plugins/infra/public/containers/logs/log_flyout.tsx b/x-pack/plugins/infra/public/containers/logs/log_flyout.tsx
index 0489892e58f2a..9ed2f5ad175c7 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_flyout.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_flyout.tsx
@@ -9,6 +9,7 @@ import { isString } from 'lodash';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { LogEntriesItem } from '../../../common/http_api';
+import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
import { UrlStateContainer } from '../../utils/url_state';
import { useTrackedPromise } from '../../utils/use_tracked_promise';
import { fetchLogEntriesItem } from './log_entries/api/fetch_log_entries_item';
@@ -26,6 +27,7 @@ export interface FlyoutOptionsUrlState {
}
export const useLogFlyout = () => {
+ const { services } = useKibanaContextForPlugin();
const { sourceId } = useLogSourceContext();
const [flyoutVisible, setFlyoutVisibility] = useState(false);
const [flyoutId, setFlyoutId] = useState(null);
@@ -39,7 +41,7 @@ export const useLogFlyout = () => {
if (!flyoutId) {
return;
}
- return await fetchLogEntriesItem({ sourceId, id: flyoutId });
+ return await fetchLogEntriesItem({ sourceId, id: flyoutId }, services.http.fetch);
},
onResolve: (response) => {
if (response) {
diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts b/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts
index 030a9d180c7b5..25865a30467f5 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_entries_highlights.ts
@@ -4,12 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
-import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
import {
LOG_ENTRIES_HIGHLIGHTS_PATH,
@@ -18,14 +15,14 @@ import {
logEntriesHighlightsResponseRT,
} from '../../../../../common/http_api';
-export const fetchLogEntriesHighlights = async (requestArgs: LogEntriesHighlightsRequest) => {
- const response = await npStart.http.fetch(LOG_ENTRIES_HIGHLIGHTS_PATH, {
+export const fetchLogEntriesHighlights = async (
+ requestArgs: LogEntriesHighlightsRequest,
+ fetch: HttpHandler
+) => {
+ const response = await fetch(LOG_ENTRIES_HIGHLIGHTS_PATH, {
method: 'POST',
body: JSON.stringify(logEntriesHighlightsRequestRT.encode(requestArgs)),
});
- return pipe(
- logEntriesHighlightsResponseRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(logEntriesHighlightsResponseRT)(response);
};
diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts b/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts
index bda8f535549c7..1cf95bc08a521 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/api/fetch_log_summary_highlights.ts
@@ -3,11 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
-import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
+
+import type { HttpHandler } from 'src/core/public';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
import {
LOG_ENTRIES_SUMMARY_HIGHLIGHTS_PATH,
@@ -17,15 +15,13 @@ import {
} from '../../../../../common/http_api';
export const fetchLogSummaryHighlights = async (
- requestArgs: LogEntriesSummaryHighlightsRequest
+ requestArgs: LogEntriesSummaryHighlightsRequest,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch(LOG_ENTRIES_SUMMARY_HIGHLIGHTS_PATH, {
+ const response = await fetch(LOG_ENTRIES_SUMMARY_HIGHLIGHTS_PATH, {
method: 'POST',
body: JSON.stringify(logEntriesSummaryHighlightsRequestRT.encode(requestArgs)),
});
- return pipe(
- logEntriesSummaryHighlightsResponseRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(logEntriesSummaryHighlightsResponseRT)(response);
};
diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx
index dbeb8c71c11eb..b4edebe8f8207 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx
@@ -10,6 +10,7 @@ import { TimeKey } from '../../../../common/time';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { fetchLogEntriesHighlights } from './api/fetch_log_entries_highlights';
import { LogEntry, LogEntriesHighlightsResponse } from '../../../../common/http_api';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
export const useLogEntryHighlights = (
sourceId: string,
@@ -21,6 +22,7 @@ export const useLogEntryHighlights = (
filterQuery: string | null,
highlightTerms: string[]
) => {
+ const { services } = useKibanaContextForPlugin();
const [logEntryHighlights, setLogEntryHighlights] = useState<
LogEntriesHighlightsResponse['data']
>([]);
@@ -32,15 +34,18 @@ export const useLogEntryHighlights = (
throw new Error('Skipping request: Insufficient parameters');
}
- return await fetchLogEntriesHighlights({
- sourceId,
- startTimestamp,
- endTimestamp,
- center: centerPoint,
- size,
- query: filterQuery || undefined,
- highlightTerms,
- });
+ return await fetchLogEntriesHighlights(
+ {
+ sourceId,
+ startTimestamp,
+ endTimestamp,
+ center: centerPoint,
+ size,
+ query: filterQuery || undefined,
+ highlightTerms,
+ },
+ services.http.fetch
+ );
},
onResolve: (response) => {
setLogEntryHighlights(response.data);
diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts
index 6d982ee004ccc..14366891dbf59 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts
@@ -11,6 +11,7 @@ import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { fetchLogSummaryHighlights } from './api/fetch_log_summary_highlights';
import { LogEntriesSummaryHighlightsResponse } from '../../../../common/http_api';
import { useBucketSize } from '../log_summary/bucket_size';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
export const useLogSummaryHighlights = (
sourceId: string,
@@ -20,6 +21,7 @@ export const useLogSummaryHighlights = (
filterQuery: string | null,
highlightTerms: string[]
) => {
+ const { services } = useKibanaContextForPlugin();
const [logSummaryHighlights, setLogSummaryHighlights] = useState<
LogEntriesSummaryHighlightsResponse['data']
>([]);
@@ -34,14 +36,17 @@ export const useLogSummaryHighlights = (
throw new Error('Skipping request: Insufficient parameters');
}
- return await fetchLogSummaryHighlights({
- sourceId,
- startTimestamp,
- endTimestamp,
- bucketSize,
- query: filterQuery,
- highlightTerms,
- });
+ return await fetchLogSummaryHighlights(
+ {
+ sourceId,
+ startTimestamp,
+ endTimestamp,
+ bucketSize,
+ query: filterQuery,
+ highlightTerms,
+ },
+ services.http.fetch
+ );
},
onResolve: (response) => {
setLogSummaryHighlights(response.data);
diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_configuration.ts b/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_configuration.ts
index e847302a6d367..c9ced069473a3 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_configuration.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_configuration.ts
@@ -4,17 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { HttpSetup } from 'src/core/public';
+import type { HttpHandler } from 'src/core/public';
import {
getLogSourceConfigurationPath,
getLogSourceConfigurationSuccessResponsePayloadRT,
} from '../../../../../common/http_api/log_sources';
import { decodeOrThrow } from '../../../../../common/runtime_types';
-export const callFetchLogSourceConfigurationAPI = async (
- sourceId: string,
- fetch: HttpSetup['fetch']
-) => {
+export const callFetchLogSourceConfigurationAPI = async (sourceId: string, fetch: HttpHandler) => {
const response = await fetch(getLogSourceConfigurationPath(sourceId), {
method: 'GET',
});
diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_status.ts b/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_status.ts
index 20e67a0a59c9f..5bc409115e595 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_status.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_source/api/fetch_log_source_status.ts
@@ -4,14 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { HttpSetup } from 'src/core/public';
+import type { HttpHandler } from 'src/core/public';
import {
getLogSourceStatusPath,
getLogSourceStatusSuccessResponsePayloadRT,
} from '../../../../../common/http_api/log_sources';
import { decodeOrThrow } from '../../../../../common/runtime_types';
-export const callFetchLogSourceStatusAPI = async (sourceId: string, fetch: HttpSetup['fetch']) => {
+export const callFetchLogSourceStatusAPI = async (sourceId: string, fetch: HttpHandler) => {
const response = await fetch(getLogSourceStatusPath(sourceId), {
method: 'GET',
});
diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/api/patch_log_source_configuration.ts b/x-pack/plugins/infra/public/containers/logs/log_source/api/patch_log_source_configuration.ts
index 4361e4bef827f..33212c5d3b0f2 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_source/api/patch_log_source_configuration.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_source/api/patch_log_source_configuration.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { HttpSetup } from 'src/core/public';
+import type { HttpHandler } from 'src/core/public';
import {
getLogSourceConfigurationPath,
patchLogSourceConfigurationSuccessResponsePayloadRT,
@@ -16,7 +16,7 @@ import { decodeOrThrow } from '../../../../../common/runtime_types';
export const callPatchLogSourceConfigurationAPI = async (
sourceId: string,
patchedProperties: LogSourceConfigurationPropertiesPatch,
- fetch: HttpSetup['fetch']
+ fetch: HttpHandler
) => {
const response = await fetch(getLogSourceConfigurationPath(sourceId), {
method: 'PATCH',
diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts
index 51b32a4c4eacf..e2dd4c523c03f 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts
@@ -7,7 +7,7 @@
import createContainer from 'constate';
import { useCallback, useMemo, useState } from 'react';
import { useMountedState } from 'react-use';
-import { HttpSetup } from 'src/core/public';
+import type { HttpHandler } from 'src/core/public';
import {
LogSourceConfiguration,
LogSourceConfigurationProperties,
@@ -26,13 +26,7 @@ export {
LogSourceStatus,
};
-export const useLogSource = ({
- sourceId,
- fetch,
-}: {
- sourceId: string;
- fetch: HttpSetup['fetch'];
-}) => {
+export const useLogSource = ({ sourceId, fetch }: { sourceId: string; fetch: HttpHandler }) => {
const getIsMounted = useMountedState();
const [sourceConfiguration, setSourceConfiguration] = useState<
LogSourceConfiguration | undefined
diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts
index b414408512db2..4a6da6063e960 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts
@@ -9,6 +9,7 @@ import { esKuery } from '../../../../../../../src/plugins/data/public';
import { fetchLogEntries } from '../log_entries/api/fetch_log_entries';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { LogEntry, LogEntriesCursor } from '../../../../common/http_api';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
interface LogStreamProps {
sourceId: string;
@@ -31,6 +32,7 @@ export function useLogStream({
query,
center,
}: LogStreamProps): LogStreamState {
+ const { services } = useKibanaContextForPlugin();
const [entries, setEntries] = useState([]);
const parsedQuery = useMemo(() => {
@@ -47,13 +49,16 @@ export function useLogStream({
setEntries([]);
const fetchPosition = center ? { center } : { before: 'last' };
- return fetchLogEntries({
- sourceId,
- startTimestamp,
- endTimestamp,
- query: parsedQuery,
- ...fetchPosition,
- });
+ return fetchLogEntries(
+ {
+ sourceId,
+ startTimestamp,
+ endTimestamp,
+ query: parsedQuery,
+ ...fetchPosition,
+ },
+ services.http.fetch
+ );
},
onResolve: ({ data }) => {
setEntries(data.entries);
diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/api/fetch_log_summary.ts b/x-pack/plugins/infra/public/containers/logs/log_summary/api/fetch_log_summary.ts
index f74f0dc0e3117..2be6538e21ebe 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_summary/api/fetch_log_summary.ts
+++ b/x-pack/plugins/infra/public/containers/logs/log_summary/api/fetch_log_summary.ts
@@ -4,11 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
-import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
+import type { HttpHandler } from 'src/core/public';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
import {
LOG_ENTRIES_SUMMARY_PATH,
@@ -17,14 +14,14 @@ import {
logEntriesSummaryResponseRT,
} from '../../../../../common/http_api';
-export const fetchLogSummary = async (requestArgs: LogEntriesSummaryRequest) => {
- const response = await npStart.http.fetch(LOG_ENTRIES_SUMMARY_PATH, {
+export const fetchLogSummary = async (
+ requestArgs: LogEntriesSummaryRequest,
+ fetch: HttpHandler
+) => {
+ const response = await fetch(LOG_ENTRIES_SUMMARY_PATH, {
method: 'POST',
body: JSON.stringify(logEntriesSummaryRequestRT.encode(requestArgs)),
});
- return pipe(
- logEntriesSummaryResponseRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(logEntriesSummaryResponseRT)(response);
};
diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx
index 73d0e5efdf06b..652ea8c71dc44 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.test.tsx
@@ -5,6 +5,8 @@
*/
import { renderHook } from '@testing-library/react-hooks';
+// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock`
+import { coreMock as mockCoreMock } from 'src/core/public/mocks';
import { useLogSummary } from './log_summary';
@@ -16,6 +18,10 @@ import { datemathToEpochMillis } from '../../../utils/datemath';
jest.mock('./api/fetch_log_summary', () => ({ fetchLogSummary: jest.fn() }));
const fetchLogSummaryMock = fetchLogSummary as jest.MockedFunction;
+jest.mock('../../../hooks/use_kibana', () => ({
+ useKibanaContextForPlugin: () => ({ services: mockCoreMock.createStart() }),
+}));
+
describe('useLogSummary hook', () => {
beforeEach(() => {
fetchLogSummaryMock.mockClear();
@@ -53,7 +59,8 @@ describe('useLogSummary hook', () => {
expect(fetchLogSummaryMock).toHaveBeenLastCalledWith(
expect.objectContaining({
sourceId: 'INITIAL_SOURCE_ID',
- })
+ }),
+ expect.anything()
);
expect(result.current.buckets).toEqual(firstMockResponse.data.buckets);
@@ -64,7 +71,8 @@ describe('useLogSummary hook', () => {
expect(fetchLogSummaryMock).toHaveBeenLastCalledWith(
expect.objectContaining({
sourceId: 'CHANGED_SOURCE_ID',
- })
+ }),
+ expect.anything()
);
expect(result.current.buckets).toEqual(secondMockResponse.data.buckets);
});
@@ -96,7 +104,8 @@ describe('useLogSummary hook', () => {
expect(fetchLogSummaryMock).toHaveBeenLastCalledWith(
expect.objectContaining({
query: 'INITIAL_FILTER_QUERY',
- })
+ }),
+ expect.anything()
);
expect(result.current.buckets).toEqual(firstMockResponse.data.buckets);
@@ -107,7 +116,8 @@ describe('useLogSummary hook', () => {
expect(fetchLogSummaryMock).toHaveBeenLastCalledWith(
expect.objectContaining({
query: 'CHANGED_FILTER_QUERY',
- })
+ }),
+ expect.anything()
);
expect(result.current.buckets).toEqual(secondMockResponse.data.buckets);
});
@@ -132,7 +142,8 @@ describe('useLogSummary hook', () => {
expect.objectContaining({
startTimestamp: firstRange.startTimestamp,
endTimestamp: firstRange.endTimestamp,
- })
+ }),
+ expect.anything()
);
const secondRange = createMockDateRange('now-20s', 'now');
@@ -145,7 +156,8 @@ describe('useLogSummary hook', () => {
expect.objectContaining({
startTimestamp: secondRange.startTimestamp,
endTimestamp: secondRange.endTimestamp,
- })
+ }),
+ expect.anything()
);
});
});
diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx
index b83be77656863..be0d87f5d267d 100644
--- a/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx
+++ b/x-pack/plugins/infra/public/containers/logs/log_summary/log_summary.tsx
@@ -10,6 +10,7 @@ import { useCancellableEffect } from '../../../utils/cancellable_effect';
import { fetchLogSummary } from './api/fetch_log_summary';
import { LogEntriesSummaryResponse } from '../../../../common/http_api';
import { useBucketSize } from './bucket_size';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
export type LogSummaryBuckets = LogEntriesSummaryResponse['data']['buckets'];
@@ -19,6 +20,7 @@ export const useLogSummary = (
endTimestamp: number | null,
filterQuery: string | null
) => {
+ const { services } = useKibanaContextForPlugin();
const [logSummaryBuckets, setLogSummaryBuckets] = useState([]);
const bucketSize = useBucketSize(startTimestamp, endTimestamp);
@@ -28,13 +30,16 @@ export const useLogSummary = (
return;
}
- fetchLogSummary({
- sourceId,
- startTimestamp,
- endTimestamp,
- bucketSize,
- query: filterQuery,
- }).then((response) => {
+ fetchLogSummary(
+ {
+ sourceId,
+ startTimestamp,
+ endTimestamp,
+ bucketSize,
+ query: filterQuery,
+ },
+ services.http.fetch
+ ).then((response) => {
if (!getIsCancelled()) {
setLogSummaryBuckets(response.data.buckets);
}
diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts
index 23fa338e74f14..fa7d8f14c6a9a 100644
--- a/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts
+++ b/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts
@@ -5,21 +5,24 @@
*/
import * as rt from 'io-ts';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../legacy_singletons';
-
+import type { HttpHandler } from 'src/core/public';
import { getDatafeedId, getJobId } from '../../../../common/infra_ml';
-import { throwErrors, createPlainError } from '../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../common/runtime_types';
+
+interface DeleteJobsRequestArgs {
+ spaceId: string;
+ sourceId: string;
+ jobTypes: JobType[];
+}
export const callDeleteJobs = async (
- spaceId: string,
- sourceId: string,
- jobTypes: JobType[]
+ requestArgs: DeleteJobsRequestArgs,
+ fetch: HttpHandler
) => {
+ const { spaceId, sourceId, jobTypes } = requestArgs;
+
// NOTE: Deleting the jobs via this API will delete the datafeeds at the same time
- const deleteJobsResponse = await npStart.http.fetch('/api/ml/jobs/delete_jobs', {
+ const deleteJobsResponse = await fetch('/api/ml/jobs/delete_jobs', {
method: 'POST',
body: JSON.stringify(
deleteJobsRequestPayloadRT.encode({
@@ -28,28 +31,29 @@ export const callDeleteJobs = async (
),
});
- return pipe(
- deleteJobsResponsePayloadRT.decode(deleteJobsResponse),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(deleteJobsResponsePayloadRT)(deleteJobsResponse);
};
-export const callGetJobDeletionTasks = async () => {
- const jobDeletionTasksResponse = await npStart.http.fetch('/api/ml/jobs/deleting_jobs_tasks');
+export const callGetJobDeletionTasks = async (fetch: HttpHandler) => {
+ const jobDeletionTasksResponse = await fetch('/api/ml/jobs/deleting_jobs_tasks');
- return pipe(
- getJobDeletionTasksResponsePayloadRT.decode(jobDeletionTasksResponse),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getJobDeletionTasksResponsePayloadRT)(jobDeletionTasksResponse);
};
+interface StopDatafeedsRequestArgs {
+ spaceId: string;
+ sourceId: string;
+ jobTypes: JobType[];
+}
+
export const callStopDatafeeds = async (
- spaceId: string,
- sourceId: string,
- jobTypes: JobType[]
+ requestArgs: StopDatafeedsRequestArgs,
+ fetch: HttpHandler
) => {
+ const { spaceId, sourceId, jobTypes } = requestArgs;
+
// Stop datafeed due to https://github.com/elastic/kibana/issues/44652
- const stopDatafeedResponse = await npStart.http.fetch('/api/ml/jobs/stop_datafeeds', {
+ const stopDatafeedResponse = await fetch('/api/ml/jobs/stop_datafeeds', {
method: 'POST',
body: JSON.stringify(
stopDatafeedsRequestPayloadRT.encode({
@@ -58,10 +62,7 @@ export const callStopDatafeeds = async (
),
});
- return pipe(
- stopDatafeedsResponsePayloadRT.decode(stopDatafeedResponse),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(stopDatafeedsResponsePayloadRT)(stopDatafeedResponse);
};
export const deleteJobsRequestPayloadRT = rt.type({
diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts
index 3fddb63f69791..84b5df3d172c7 100644
--- a/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts
+++ b/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts
@@ -4,21 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
-import { npStart } from '../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { getJobId, jobCustomSettingsRT } from '../../../../common/infra_ml';
-import { createPlainError, throwErrors } from '../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../common/runtime_types';
+
+interface RequestArgs {
+ spaceId: string;
+ sourceId: string;
+ jobTypes: JobType[];
+}
export const callJobsSummaryAPI = async (
- spaceId: string,
- sourceId: string,
- jobTypes: JobType[]
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch('/api/ml/jobs/jobs_summary', {
+ const { spaceId, sourceId, jobTypes } = requestArgs;
+ const response = await fetch('/api/ml/jobs/jobs_summary', {
method: 'POST',
body: JSON.stringify(
fetchJobStatusRequestPayloadRT.encode({
@@ -26,10 +29,7 @@ export const callJobsSummaryAPI = async (
})
),
});
- return pipe(
- fetchJobStatusResponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(fetchJobStatusResponsePayloadRT)(response);
};
export const fetchJobStatusRequestPayloadRT = rt.type({
diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts
index d492522c120a1..75ce335fbe49c 100644
--- a/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts
+++ b/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts
@@ -4,24 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
-import { npStart } from '../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { jobCustomSettingsRT } from '../../../../common/log_analysis';
-import { createPlainError, throwErrors } from '../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../common/runtime_types';
-export const callGetMlModuleAPI = async (moduleId: string) => {
- const response = await npStart.http.fetch(`/api/ml/modules/get_module/${moduleId}`, {
+export const callGetMlModuleAPI = async (moduleId: string, fetch: HttpHandler) => {
+ const response = await fetch(`/api/ml/modules/get_module/${moduleId}`, {
method: 'GET',
});
- return pipe(
- getMlModuleResponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getMlModuleResponsePayloadRT)(response);
};
const jobDefinitionRT = rt.type({
diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts
index 06b0e075387b0..36dced1bd2680 100644
--- a/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts
+++ b/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts
@@ -4,27 +4,38 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { identity } from 'fp-ts/lib/function';
-import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
-import { npStart } from '../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { getJobIdPrefix, jobCustomSettingsRT } from '../../../../common/infra_ml';
-import { createPlainError, throwErrors } from '../../../../common/runtime_types';
-
-export const callSetupMlModuleAPI = async (
- moduleId: string,
- start: number | undefined,
- end: number | undefined,
- spaceId: string,
- sourceId: string,
- indexPattern: string,
- jobOverrides: SetupMlModuleJobOverrides[] = [],
- datafeedOverrides: SetupMlModuleDatafeedOverrides[] = [],
- query?: object
-) => {
- const response = await npStart.http.fetch(`/api/ml/modules/setup/${moduleId}`, {
+import { decodeOrThrow } from '../../../../common/runtime_types';
+
+interface RequestArgs {
+ moduleId: string;
+ start?: number;
+ end?: number;
+ spaceId: string;
+ sourceId: string;
+ indexPattern: string;
+ jobOverrides?: SetupMlModuleJobOverrides[];
+ datafeedOverrides?: SetupMlModuleDatafeedOverrides[];
+ query?: object;
+}
+
+export const callSetupMlModuleAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
+ const {
+ moduleId,
+ start,
+ end,
+ spaceId,
+ sourceId,
+ indexPattern,
+ jobOverrides = [],
+ datafeedOverrides = [],
+ query,
+ } = requestArgs;
+
+ const response = await fetch(`/api/ml/modules/setup/${moduleId}`, {
method: 'POST',
body: JSON.stringify(
setupMlModuleRequestPayloadRT.encode({
@@ -40,10 +51,7 @@ export const callSetupMlModuleAPI = async (
),
});
- return pipe(
- setupMlModuleResponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(setupMlModuleResponsePayloadRT)(response);
};
const setupMlModuleTimeParamsRT = rt.partial({
diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx
index f4c90a459af6a..bc488a51e2aff 100644
--- a/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx
+++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx
@@ -10,14 +10,15 @@ import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { useTrackedPromise } from '../../utils/use_tracked_promise';
-import { npStart } from '../../legacy_singletons';
import {
getMlCapabilitiesResponsePayloadRT,
GetMlCapabilitiesResponsePayload,
} from './api/ml_api_types';
import { throwErrors, createPlainError } from '../../../common/runtime_types';
+import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
export const useInfraMLCapabilities = () => {
+ const { services } = useKibanaContextForPlugin();
const [mlCapabilities, setMlCapabilities] = useState(
initialMlCapabilities
);
@@ -26,7 +27,7 @@ export const useInfraMLCapabilities = () => {
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
- const rawResponse = await npStart.http.fetch('/api/ml/ml_capabilities');
+ const rawResponse = await services.http.fetch('/api/ml/ml_capabilities');
return pipe(
getMlCapabilitiesResponsePayloadRT.decode(rawResponse),
diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx
index 736982c8043b1..871e61ecfe507 100644
--- a/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx
+++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx
@@ -4,16 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { HttpHandler } from 'src/core/public';
import { getJobId } from '../../../common/infra_ml';
import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './api/ml_cleanup';
export const cleanUpJobsAndDatafeeds = async (
spaceId: string,
sourceId: string,
- jobTypes: JobType[]
+ jobTypes: JobType[],
+ fetch: HttpHandler
) => {
try {
- await callStopDatafeeds(spaceId, sourceId, jobTypes);
+ await callStopDatafeeds({ spaceId, sourceId, jobTypes }, fetch);
} catch (err) {
// Proceed only if datafeed has been deleted or didn't exist in the first place
if (err?.res?.status !== 404) {
@@ -21,27 +23,29 @@ export const cleanUpJobsAndDatafeeds = async (
}
}
- return await deleteJobs(spaceId, sourceId, jobTypes);
+ return await deleteJobs(spaceId, sourceId, jobTypes, fetch);
};
const deleteJobs = async (
spaceId: string,
sourceId: string,
- jobTypes: JobType[]
+ jobTypes: JobType[],
+ fetch: HttpHandler
) => {
- const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId, jobTypes);
- await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes);
+ const deleteJobsResponse = await callDeleteJobs({ spaceId, sourceId, jobTypes }, fetch);
+ await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes, fetch);
return deleteJobsResponse;
};
const waitUntilJobsAreDeleted = async (
spaceId: string,
sourceId: string,
- jobTypes: JobType[]
+ jobTypes: JobType[],
+ fetch: HttpHandler
) => {
const moduleJobIds = jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType));
while (true) {
- const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks();
+ const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks(fetch);
const needToWait = jobIdsBeingDeleted.some((jobId) => moduleJobIds.includes(jobId));
if (needToWait) {
diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx
index 349541d108f5e..5408084a5246e 100644
--- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx
+++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx
@@ -6,6 +6,7 @@
import { useCallback, useMemo } from 'react';
import { DatasetFilter } from '../../../common/infra_ml';
+import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
import { useTrackedPromise } from '../../utils/use_tracked_promise';
import { useModuleStatus } from './infra_ml_module_status';
import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types';
@@ -17,6 +18,7 @@ export const useInfraMLModule = ({
sourceConfiguration: ModuleSourceConfiguration;
moduleDescriptor: ModuleDescriptor;
}) => {
+ const { services } = useKibanaContextForPlugin();
const { spaceId, sourceId, timestampField } = sourceConfiguration;
const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes);
@@ -25,7 +27,7 @@ export const useInfraMLModule = ({
cancelPreviousOn: 'resolution',
createPromise: async () => {
dispatchModuleStatus({ type: 'fetchingJobStatuses' });
- return await moduleDescriptor.getJobSummary(spaceId, sourceId);
+ return await moduleDescriptor.getJobSummary(spaceId, sourceId, services.http.fetch);
},
onResolve: (jobResponse) => {
dispatchModuleStatus({
@@ -54,18 +56,25 @@ export const useInfraMLModule = ({
) => {
dispatchModuleStatus({ type: 'startedSetup' });
const setupResult = await moduleDescriptor.setUpModule(
- start,
- end,
- datasetFilter,
{
- indices: selectedIndices,
- sourceId,
- spaceId,
- timestampField,
+ start,
+ end,
+ datasetFilter,
+ moduleSourceConfiguration: {
+ indices: selectedIndices,
+ sourceId,
+ spaceId,
+ timestampField,
+ },
+ partitionField,
},
- partitionField
+ services.http.fetch
+ );
+ const jobSummaries = await moduleDescriptor.getJobSummary(
+ spaceId,
+ sourceId,
+ services.http.fetch
);
- const jobSummaries = await moduleDescriptor.getJobSummary(spaceId, sourceId);
return { setupResult, jobSummaries };
},
onResolve: ({ setupResult: { datafeeds, jobs }, jobSummaries }) => {
@@ -89,7 +98,7 @@ export const useInfraMLModule = ({
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
- return await moduleDescriptor.cleanUpModule(spaceId, sourceId);
+ return await moduleDescriptor.cleanUpModule(spaceId, sourceId, services.http.fetch);
},
},
[spaceId, sourceId]
diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx
index 3c7ffcfd4a4e2..a747a2853d1f7 100644
--- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx
+++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx
@@ -6,6 +6,7 @@
import { useCallback, useMemo, useState } from 'react';
import { getJobId } from '../../../common/log_analysis';
+import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
import { useTrackedPromise } from '../../utils/use_tracked_promise';
import { JobSummary } from './api/ml_get_jobs_summary_api';
import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module';
@@ -18,6 +19,7 @@ export const useInfraMLModuleDefinition = ({
sourceConfiguration: ModuleSourceConfiguration;
moduleDescriptor: ModuleDescriptor;
}) => {
+ const { services } = useKibanaContextForPlugin();
const [moduleDefinition, setModuleDefinition] = useState<
GetMlModuleResponsePayload | undefined
>();
@@ -40,7 +42,7 @@ export const useInfraMLModuleDefinition = ({
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
- return await moduleDescriptor.getModuleDefinition();
+ return await moduleDescriptor.getModuleDefinition(services.http.fetch);
},
onResolve: (response) => {
setModuleDefinition(response);
diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts
index e36f38add641a..976a64e8034bc 100644
--- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts
+++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
+import { HttpHandler } from 'src/core/public';
import {
ValidateLogEntryDatasetsResponsePayload,
ValidationIndicesResponsePayload,
@@ -16,6 +16,14 @@ import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api';
export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api';
+export interface SetUpModuleArgs {
+ start?: number | undefined;
+ end?: number | undefined;
+ datasetFilter?: DatasetFilter;
+ moduleSourceConfiguration: ModuleSourceConfiguration;
+ partitionField?: string;
+}
+
export interface ModuleDescriptor {
moduleId: string;
moduleName: string;
@@ -23,25 +31,32 @@ export interface ModuleDescriptor {
jobTypes: JobType[];
bucketSpan: number;
getJobIds: (spaceId: string, sourceId: string) => Record;
- getJobSummary: (spaceId: string, sourceId: string) => Promise;
- getModuleDefinition: () => Promise;
+ getJobSummary: (
+ spaceId: string,
+ sourceId: string,
+ fetch: HttpHandler
+ ) => Promise;
+ getModuleDefinition: (fetch: HttpHandler) => Promise;
setUpModule: (
- start: number | undefined,
- end: number | undefined,
- datasetFilter: DatasetFilter,
- sourceConfiguration: ModuleSourceConfiguration,
- partitionField?: string
+ setUpModuleArgs: SetUpModuleArgs,
+ fetch: HttpHandler
) => Promise;
- cleanUpModule: (spaceId: string, sourceId: string) => Promise;
+ cleanUpModule: (
+ spaceId: string,
+ sourceId: string,
+ fetch: HttpHandler
+ ) => Promise;
validateSetupIndices?: (
indices: string[],
- timestampField: string
+ timestampField: string,
+ fetch: HttpHandler
) => Promise;
validateSetupDatasets?: (
indices: string[],
timestampField: string,
startTime: number,
- endTime: number
+ endTime: number,
+ fetch: HttpHandler
) => Promise;
}
diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts
index 7ea87c3d21322..47230cbed977f 100644
--- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts
+++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts
@@ -5,7 +5,8 @@
*/
import { i18n } from '@kbn/i18n';
-import { ModuleDescriptor, ModuleSourceConfiguration } from '../../infra_ml_module_types';
+import { HttpHandler } from 'src/core/public';
+import { ModuleDescriptor, SetUpModuleArgs } from '../../infra_ml_module_types';
import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup';
import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
import { callGetMlModuleAPI } from '../../api/ml_get_module';
@@ -14,7 +15,6 @@ import {
metricsHostsJobTypes,
getJobId,
MetricsHostsJobType,
- DatasetFilter,
bucketSpan,
} from '../../../../../common/infra_ml';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
@@ -48,24 +48,28 @@ const getJobIds = (spaceId: string, sourceId: string) =>
{} as Record
);
-const getJobSummary = async (spaceId: string, sourceId: string) => {
- const response = await callJobsSummaryAPI(spaceId, sourceId, metricsHostsJobTypes);
+const getJobSummary = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ const response = await callJobsSummaryAPI(
+ { spaceId, sourceId, jobTypes: metricsHostsJobTypes },
+ fetch
+ );
const jobIds = Object.values(getJobIds(spaceId, sourceId));
return response.filter((jobSummary) => jobIds.includes(jobSummary.id));
};
-const getModuleDefinition = async () => {
- return await callGetMlModuleAPI(moduleId);
+const getModuleDefinition = async (fetch: HttpHandler) => {
+ return await callGetMlModuleAPI(moduleId, fetch);
};
-const setUpModule = async (
- start: number | undefined,
- end: number | undefined,
- datasetFilter: DatasetFilter,
- { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
- partitionField?: string
-) => {
+const setUpModule = async (setUpModuleArgs: SetUpModuleArgs, fetch: HttpHandler) => {
+ const {
+ start,
+ end,
+ moduleSourceConfiguration: { spaceId, sourceId, indices, timestampField },
+ partitionField,
+ } = setUpModuleArgs;
+
const indexNamePattern = indices.join(',');
const jobIds: JobType[] = ['hosts_memory_usage', 'hosts_network_in', 'hosts_network_out'];
@@ -128,14 +132,17 @@ const setUpModule = async (
});
return callSetupMlModuleAPI(
- moduleId,
- start,
- end,
- spaceId,
- sourceId,
- indexNamePattern,
- jobOverrides,
- datafeedOverrides
+ {
+ moduleId,
+ start,
+ end,
+ spaceId,
+ sourceId,
+ indexPattern: indexNamePattern,
+ jobOverrides,
+ datafeedOverrides,
+ },
+ fetch
);
};
@@ -159,8 +166,8 @@ const getDefaultJobConfigs = (jobId: JobType): { datafeed: any; job: any } => {
}
};
-const cleanUpModule = async (spaceId: string, sourceId: string) => {
- return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsHostsJobTypes);
+const cleanUpModule = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsHostsJobTypes, fetch);
};
export const metricHostsModule: ModuleDescriptor = {
diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts
index eaf7489c84eb4..488803dc113b0 100644
--- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts
+++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts
@@ -5,7 +5,8 @@
*/
import { i18n } from '@kbn/i18n';
-import { ModuleDescriptor, ModuleSourceConfiguration } from '../../infra_ml_module_types';
+import { HttpHandler } from 'src/core/public';
+import { ModuleDescriptor, SetUpModuleArgs } from '../../infra_ml_module_types';
import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup';
import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
import { callGetMlModuleAPI } from '../../api/ml_get_module';
@@ -14,7 +15,6 @@ import {
metricsK8SJobTypes,
getJobId,
MetricK8sJobType,
- DatasetFilter,
bucketSpan,
} from '../../../../../common/infra_ml';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
@@ -49,24 +49,28 @@ const getJobIds = (spaceId: string, sourceId: string) =>
{} as Record
);
-const getJobSummary = async (spaceId: string, sourceId: string) => {
- const response = await callJobsSummaryAPI(spaceId, sourceId, metricsK8SJobTypes);
+const getJobSummary = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ const response = await callJobsSummaryAPI(
+ { spaceId, sourceId, jobTypes: metricsK8SJobTypes },
+ fetch
+ );
const jobIds = Object.values(getJobIds(spaceId, sourceId));
return response.filter((jobSummary) => jobIds.includes(jobSummary.id));
};
-const getModuleDefinition = async () => {
- return await callGetMlModuleAPI(moduleId);
+const getModuleDefinition = async (fetch: HttpHandler) => {
+ return await callGetMlModuleAPI(moduleId, fetch);
};
-const setUpModule = async (
- start: number | undefined,
- end: number | undefined,
- datasetFilter: DatasetFilter,
- { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
- partitionField?: string
-) => {
+const setUpModule = async (setUpModuleArgs: SetUpModuleArgs, fetch: HttpHandler) => {
+ const {
+ start,
+ end,
+ moduleSourceConfiguration: { spaceId, sourceId, indices, timestampField },
+ partitionField,
+ } = setUpModuleArgs;
+
const indexNamePattern = indices.join(',');
const jobIds: JobType[] = ['k8s_memory_usage', 'k8s_network_in', 'k8s_network_out'];
const jobOverrides = jobIds.map((id) => {
@@ -133,14 +137,17 @@ const setUpModule = async (
});
return callSetupMlModuleAPI(
- moduleId,
- start,
- end,
- spaceId,
- sourceId,
- indexNamePattern,
- jobOverrides,
- datafeedOverrides
+ {
+ moduleId,
+ start,
+ end,
+ spaceId,
+ sourceId,
+ indexPattern: indexNamePattern,
+ jobOverrides,
+ datafeedOverrides,
+ },
+ fetch
);
};
@@ -164,8 +171,8 @@ const getDefaultJobConfigs = (jobId: JobType): { datafeed: any; job: any } => {
}
};
-const cleanUpModule = async (spaceId: string, sourceId: string) => {
- return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsK8SJobTypes);
+const cleanUpModule = async (spaceId: string, sourceId: string, fetch: HttpHandler) => {
+ return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsK8SJobTypes, fetch);
};
export const metricHostsModule: ModuleDescriptor = {
diff --git a/x-pack/plugins/infra/public/legacy_singletons.ts b/x-pack/plugins/infra/public/legacy_singletons.ts
deleted file mode 100644
index f57047f21c281..0000000000000
--- a/x-pack/plugins/infra/public/legacy_singletons.ts
+++ /dev/null
@@ -1,14 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import { CoreStart } from 'kibana/public';
-
-let npStart: CoreStart;
-
-export function registerStartSingleton(start: CoreStart) {
- npStart = start;
-}
-
-export { npStart };
diff --git a/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx b/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
index 945b299674aaa..4f83e37d7e029 100644
--- a/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
+++ b/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
@@ -14,7 +14,6 @@ import { createMemoryHistory } from 'history';
import React from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { httpServiceMock } from 'src/core/public/mocks';
-// import { HttpSetup } from 'src/core/public';
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
import { useLogSource } from '../../containers/logs/log_source';
import {
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts
index a8cd7854efb6b..5f34d45635b60 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts
@@ -4,24 +4,28 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import {
getLogEntryCategoryDatasetsRequestPayloadRT,
getLogEntryCategoryDatasetsSuccessReponsePayloadRT,
LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH,
} from '../../../../../common/http_api/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+}
export const callGetLogEntryCategoryDatasetsAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH, {
+ const { sourceId, startTime, endTime } = requestArgs;
+
+ const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH, {
method: 'POST',
body: JSON.stringify(
getLogEntryCategoryDatasetsRequestPayloadRT.encode({
@@ -36,8 +40,5 @@ export const callGetLogEntryCategoryDatasetsAPI = async (
),
});
- return pipe(
- getLogEntryCategoryDatasetsSuccessReponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getLogEntryCategoryDatasetsSuccessReponsePayloadRT)(response);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts
index a10d077a2dd4f..c4b756ebf5d58 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_examples.ts
@@ -4,26 +4,30 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import {
getLogEntryCategoryExamplesRequestPayloadRT,
getLogEntryCategoryExamplesSuccessReponsePayloadRT,
LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH,
} from '../../../../../common/http_api/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+ categoryId: number;
+ exampleCount: number;
+}
export const callGetLogEntryCategoryExamplesAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number,
- categoryId: number,
- exampleCount: number
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH, {
+ const { sourceId, startTime, endTime, categoryId, exampleCount } = requestArgs;
+
+ const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH, {
method: 'POST',
body: JSON.stringify(
getLogEntryCategoryExamplesRequestPayloadRT.encode({
@@ -40,8 +44,5 @@ export const callGetLogEntryCategoryExamplesAPI = async (
),
});
- return pipe(
- getLogEntryCategoryExamplesSuccessReponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getLogEntryCategoryExamplesSuccessReponsePayloadRT)(response);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts
index 2ebcff4fd3ca5..fd53803796339 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts
@@ -4,28 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import {
getLogEntryCategoriesRequestPayloadRT,
getLogEntryCategoriesSuccessReponsePayloadRT,
LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH,
} from '../../../../../common/http_api/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
+
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+ categoryCount: number;
+ datasets?: string[];
+}
export const callGetTopLogEntryCategoriesAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number,
- categoryCount: number,
- datasets?: string[]
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
+ const { sourceId, startTime, endTime, categoryCount, datasets } = requestArgs;
const intervalDuration = endTime - startTime;
- const response = await npStart.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, {
+ const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, {
method: 'POST',
body: JSON.stringify(
getLogEntryCategoriesRequestPayloadRT.encode({
@@ -60,8 +63,5 @@ export const callGetTopLogEntryCategoriesAPI = async (
),
});
- return pipe(
- getLogEntryCategoriesSuccessReponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getLogEntryCategoriesSuccessReponsePayloadRT)(response);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts
index 123b188046b85..0a12c433db60a 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts
@@ -13,6 +13,7 @@ import {
import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise';
import { callGetTopLogEntryCategoriesAPI } from './service_calls/get_top_log_entry_categories';
import { callGetLogEntryCategoryDatasetsAPI } from './service_calls/get_log_entry_category_datasets';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
type TopLogEntryCategories = GetLogEntryCategoriesSuccessResponsePayload['data']['categories'];
type LogEntryCategoryDatasets = GetLogEntryCategoryDatasetsSuccessResponsePayload['data']['datasets'];
@@ -34,6 +35,7 @@ export const useLogEntryCategoriesResults = ({
sourceId: string;
startTime: number;
}) => {
+ const { services } = useKibanaContextForPlugin();
const [topLogEntryCategories, setTopLogEntryCategories] = useState([]);
const [logEntryCategoryDatasets, setLogEntryCategoryDatasets] = useState<
LogEntryCategoryDatasets
@@ -44,11 +46,14 @@ export const useLogEntryCategoriesResults = ({
cancelPreviousOn: 'creation',
createPromise: async () => {
return await callGetTopLogEntryCategoriesAPI(
- sourceId,
- startTime,
- endTime,
- categoriesCount,
- filteredDatasets
+ {
+ sourceId,
+ startTime,
+ endTime,
+ categoryCount: categoriesCount,
+ datasets: filteredDatasets,
+ },
+ services.http.fetch
);
},
onResolve: ({ data: { categories } }) => {
@@ -71,7 +76,10 @@ export const useLogEntryCategoriesResults = ({
{
cancelPreviousOn: 'creation',
createPromise: async () => {
- return await callGetLogEntryCategoryDatasetsAPI(sourceId, startTime, endTime);
+ return await callGetLogEntryCategoryDatasetsAPI(
+ { sourceId, startTime, endTime },
+ services.http.fetch
+ );
},
onResolve: ({ data: { datasets } }) => {
setLogEntryCategoryDatasets(datasets);
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx
index cdf3b642a8012..84b9f045288cc 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_category_examples.tsx
@@ -7,6 +7,7 @@
import { useMemo, useState } from 'react';
import { LogEntryCategoryExample } from '../../../../common/http_api';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { callGetLogEntryCategoryExamplesAPI } from './service_calls/get_log_entry_category_examples';
@@ -23,6 +24,8 @@ export const useLogEntryCategoryExamples = ({
sourceId: string;
startTime: number;
}) => {
+ const { services } = useKibanaContextForPlugin();
+
const [logEntryCategoryExamples, setLogEntryCategoryExamples] = useState<
LogEntryCategoryExample[]
>([]);
@@ -32,11 +35,14 @@ export const useLogEntryCategoryExamples = ({
cancelPreviousOn: 'creation',
createPromise: async () => {
return await callGetLogEntryCategoryExamplesAPI(
- sourceId,
- startTime,
- endTime,
- categoryId,
- exampleCount
+ {
+ sourceId,
+ startTime,
+ endTime,
+ categoryId,
+ exampleCount,
+ },
+ services.http.fetch
);
},
onResolve: ({ data: { examples } }) => {
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts
index 21696df566ed9..7f90604bfefdd 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import {
getLogEntryAnomaliesRequestPayloadRT,
getLogEntryAnomaliesSuccessReponsePayloadRT,
@@ -13,15 +13,18 @@ import {
import { decodeOrThrow } from '../../../../../common/runtime_types';
import { Sort, Pagination } from '../../../../../common/http_api/log_analysis';
-export const callGetLogEntryAnomaliesAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number,
- sort: Sort,
- pagination: Pagination,
- datasets?: string[]
-) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_PATH, {
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+ sort: Sort;
+ pagination: Pagination;
+ datasets?: string[];
+}
+
+export const callGetLogEntryAnomaliesAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
+ const { sourceId, startTime, endTime, sort, pagination, datasets } = requestArgs;
+ const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_PATH, {
method: 'POST',
body: JSON.stringify(
getLogEntryAnomaliesRequestPayloadRT.encode({
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts
index 24be5a646d103..c62bec691590c 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies_datasets.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import { decodeOrThrow } from '../../../../../common/runtime_types';
import {
getLogEntryAnomaliesDatasetsRequestPayloadRT,
@@ -12,12 +12,18 @@ import {
LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_DATASETS_PATH,
} from '../../../../../common/http_api/log_analysis';
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+}
+
export const callGetLogEntryAnomaliesDatasetsAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_DATASETS_PATH, {
+ const { sourceId, startTime, endTime } = requestArgs;
+ const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_DATASETS_PATH, {
method: 'POST',
body: JSON.stringify(
getLogEntryAnomaliesDatasetsRequestPayloadRT.encode({
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts
index a125b53f9e635..ab724a2f435b2 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_examples.ts
@@ -4,27 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import {
getLogEntryExamplesRequestPayloadRT,
getLogEntryExamplesSuccessReponsePayloadRT,
LOG_ANALYSIS_GET_LOG_ENTRY_RATE_EXAMPLES_PATH,
} from '../../../../../common/http_api/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
-export const callGetLogEntryExamplesAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number,
- dataset: string,
- exampleCount: number,
- categoryId?: string
-) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_RATE_EXAMPLES_PATH, {
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+ dataset: string;
+ exampleCount: number;
+ categoryId?: string;
+}
+
+export const callGetLogEntryExamplesAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
+ const { sourceId, startTime, endTime, dataset, exampleCount, categoryId } = requestArgs;
+ const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_RATE_EXAMPLES_PATH, {
method: 'POST',
body: JSON.stringify(
getLogEntryExamplesRequestPayloadRT.encode({
@@ -42,8 +42,5 @@ export const callGetLogEntryExamplesAPI = async (
),
});
- return pipe(
- getLogEntryExamplesSuccessReponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getLogEntryExamplesSuccessReponsePayloadRT)(response);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts
index 77111d279309d..c9189bd803955 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts
@@ -4,25 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { fold } from 'fp-ts/lib/Either';
-import { pipe } from 'fp-ts/lib/pipeable';
-import { identity } from 'fp-ts/lib/function';
-import { npStart } from '../../../../legacy_singletons';
+import type { HttpHandler } from 'src/core/public';
import {
getLogEntryRateRequestPayloadRT,
getLogEntryRateSuccessReponsePayloadRT,
LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
} from '../../../../../common/http_api/log_analysis';
-import { createPlainError, throwErrors } from '../../../../../common/runtime_types';
+import { decodeOrThrow } from '../../../../../common/runtime_types';
-export const callGetLogEntryRateAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number,
- bucketDuration: number,
- datasets?: string[]
-) => {
- const response = await npStart.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, {
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+ bucketDuration: number;
+ datasets?: string[];
+}
+
+export const callGetLogEntryRateAPI = async (requestArgs: RequestArgs, fetch: HttpHandler) => {
+ const { sourceId, startTime, endTime, bucketDuration, datasets } = requestArgs;
+ const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, {
method: 'POST',
body: JSON.stringify(
getLogEntryRateRequestPayloadRT.encode({
@@ -38,8 +38,5 @@ export const callGetLogEntryRateAPI = async (
})
),
});
- return pipe(
- getLogEntryRateSuccessReponsePayloadRT.decode(response),
- fold(throwErrors(createPlainError), identity)
- );
+ return decodeOrThrow(getLogEntryRateSuccessReponsePayloadRT)(response);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts
index 52632e54390a9..37c99272f0872 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts
@@ -16,6 +16,7 @@ import {
GetLogEntryAnomaliesDatasetsSuccessResponsePayload,
LogEntryAnomaly,
} from '../../../../common/http_api/log_analysis';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
export type SortOptions = Sort;
export type PaginationOptions = Pick;
@@ -161,6 +162,8 @@ export const useLogEntryAnomaliesResults = ({
};
};
+ const { services } = useKibanaContextForPlugin();
+
const [reducerState, dispatch] = useReducer(stateReducer, STATE_DEFAULTS, initStateReducer);
const [logEntryAnomalies, setLogEntryAnomalies] = useState([]);
@@ -177,15 +180,18 @@ export const useLogEntryAnomaliesResults = ({
filteredDatasets: queryFilteredDatasets,
} = reducerState;
return await callGetLogEntryAnomaliesAPI(
- sourceId,
- queryStartTime,
- queryEndTime,
- sortOptions,
{
- ...paginationOptions,
- cursor: paginationCursor,
+ sourceId,
+ startTime: queryStartTime,
+ endTime: queryEndTime,
+ sort: sortOptions,
+ pagination: {
+ ...paginationOptions,
+ cursor: paginationCursor,
+ },
+ datasets: queryFilteredDatasets,
},
- queryFilteredDatasets
+ services.http.fetch
);
},
onResolve: ({ data: { anomalies, paginationCursors: requestCursors, hasMoreEntries } }) => {
@@ -286,7 +292,10 @@ export const useLogEntryAnomaliesResults = ({
{
cancelPreviousOn: 'creation',
createPromise: async () => {
- return await callGetLogEntryAnomaliesDatasetsAPI(sourceId, startTime, endTime);
+ return await callGetLogEntryAnomaliesDatasetsAPI(
+ { sourceId, startTime, endTime },
+ services.http.fetch
+ );
},
onResolve: ({ data: { datasets } }) => {
setLogEntryAnomaliesDatasets(datasets);
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts
index fae5bd200a415..e809ab9cd5a6f 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts
@@ -7,6 +7,7 @@
import { useMemo, useState } from 'react';
import { LogEntryExample } from '../../../../common/http_api';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { callGetLogEntryExamplesAPI } from './service_calls/get_log_entry_examples';
@@ -25,6 +26,7 @@ export const useLogEntryExamples = ({
startTime: number;
categoryId?: string;
}) => {
+ const { services } = useKibanaContextForPlugin();
const [logEntryExamples, setLogEntryExamples] = useState([]);
const [getLogEntryExamplesRequest, getLogEntryExamples] = useTrackedPromise(
@@ -32,12 +34,15 @@ export const useLogEntryExamples = ({
cancelPreviousOn: 'creation',
createPromise: async () => {
return await callGetLogEntryExamplesAPI(
- sourceId,
- startTime,
- endTime,
- dataset,
- exampleCount,
- categoryId
+ {
+ sourceId,
+ startTime,
+ endTime,
+ dataset,
+ exampleCount,
+ categoryId,
+ },
+ services.http.fetch
);
},
onResolve: ({ data: { examples } }) => {
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts
index a52dab58cb018..aef94afa505f1 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts
@@ -12,6 +12,7 @@ import {
LogEntryRatePartition,
LogEntryRateAnomaly,
} from '../../../../common/http_api/log_analysis';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { callGetLogEntryRateAPI } from './service_calls/get_log_entry_rate';
@@ -49,6 +50,7 @@ export const useLogEntryRateResults = ({
bucketDuration: number;
filteredDatasets?: string[];
}) => {
+ const { services } = useKibanaContextForPlugin();
const [logEntryRate, setLogEntryRate] = useState(null);
const [getLogEntryRateRequest, getLogEntryRate] = useTrackedPromise(
@@ -56,11 +58,14 @@ export const useLogEntryRateResults = ({
cancelPreviousOn: 'resolution',
createPromise: async () => {
return await callGetLogEntryRateAPI(
- sourceId,
- startTime,
- endTime,
- bucketDuration,
- filteredDatasets
+ {
+ sourceId,
+ startTime,
+ endTime,
+ bucketDuration,
+ datasets: filteredDatasets,
+ },
+ services.http.fetch
);
},
onResolve: ({ data }) => {
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts
index f33e3ea16b389..02170f41a32ca 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts
@@ -5,6 +5,7 @@
*/
import { useMemo, useState, useCallback, useEffect, useReducer } from 'react';
+import { HttpHandler } from 'src/core/public';
import {
INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH,
Metric,
@@ -16,8 +17,8 @@ import {
getMetricsHostsAnomaliesSuccessReponsePayloadRT,
} from '../../../../../common/http_api/infra_ml';
import { useTrackedPromise } from '../../../../utils/use_tracked_promise';
-import { npStart } from '../../../../legacy_singletons';
import { decodeOrThrow } from '../../../../../common/runtime_types';
+import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
export type SortOptions = Sort;
export type PaginationOptions = Pick;
@@ -149,6 +150,7 @@ export const useMetricsHostsAnomaliesResults = ({
onGetMetricsHostsAnomaliesDatasetsError?: (error: Error) => void;
filteredDatasets?: string[];
}) => {
+ const { services } = useKibanaContextForPlugin();
const initStateReducer = (stateDefaults: ReducerStateDefaults): ReducerState => {
return {
...stateDefaults,
@@ -177,15 +179,18 @@ export const useMetricsHostsAnomaliesResults = ({
paginationCursor,
} = reducerState;
return await callGetMetricHostsAnomaliesAPI(
- sourceId,
- queryStartTime,
- queryEndTime,
- metric,
- sortOptions,
{
- ...paginationOptions,
- cursor: paginationCursor,
- }
+ sourceId,
+ startTime: queryStartTime,
+ endTime: queryEndTime,
+ metric,
+ sort: sortOptions,
+ pagination: {
+ ...paginationOptions,
+ cursor: paginationCursor,
+ },
+ },
+ services.http.fetch
);
},
onResolve: ({ data: { anomalies, paginationCursors: requestCursors, hasMoreEntries } }) => {
@@ -288,15 +293,21 @@ export const useMetricsHostsAnomaliesResults = ({
};
};
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+ metric: Metric;
+ sort: Sort;
+ pagination: Pagination;
+}
+
export const callGetMetricHostsAnomaliesAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number,
- metric: Metric,
- sort: Sort,
- pagination: Pagination
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch(INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, {
+ const { sourceId, startTime, endTime, metric, sort, pagination } = requestArgs;
+ const response = await fetch(INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, {
method: 'POST',
body: JSON.stringify(
getMetricsHostsAnomaliesRequestPayloadRT.encode({
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts
index 89e70c4c5c4c7..951951b9b6106 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts
@@ -5,6 +5,7 @@
*/
import { useMemo, useState, useCallback, useEffect, useReducer } from 'react';
+import { HttpHandler } from 'src/core/public';
import {
Sort,
Pagination,
@@ -16,8 +17,8 @@ import {
Metric,
} from '../../../../../common/http_api/infra_ml';
import { useTrackedPromise } from '../../../../utils/use_tracked_promise';
-import { npStart } from '../../../../legacy_singletons';
import { decodeOrThrow } from '../../../../../common/runtime_types';
+import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
export type SortOptions = Sort;
export type PaginationOptions = Pick;
@@ -149,6 +150,7 @@ export const useMetricsK8sAnomaliesResults = ({
onGetMetricsHostsAnomaliesDatasetsError?: (error: Error) => void;
filteredDatasets?: string[];
}) => {
+ const { services } = useKibanaContextForPlugin();
const initStateReducer = (stateDefaults: ReducerStateDefaults): ReducerState => {
return {
...stateDefaults,
@@ -178,16 +180,19 @@ export const useMetricsK8sAnomaliesResults = ({
filteredDatasets: queryFilteredDatasets,
} = reducerState;
return await callGetMetricsK8sAnomaliesAPI(
- sourceId,
- queryStartTime,
- queryEndTime,
- metric,
- sortOptions,
{
- ...paginationOptions,
- cursor: paginationCursor,
+ sourceId,
+ startTime: queryStartTime,
+ endTime: queryEndTime,
+ metric,
+ sort: sortOptions,
+ pagination: {
+ ...paginationOptions,
+ cursor: paginationCursor,
+ },
+ datasets: queryFilteredDatasets,
},
- queryFilteredDatasets
+ services.http.fetch
);
},
onResolve: ({ data: { anomalies, paginationCursors: requestCursors, hasMoreEntries } }) => {
@@ -290,16 +295,22 @@ export const useMetricsK8sAnomaliesResults = ({
};
};
+interface RequestArgs {
+ sourceId: string;
+ startTime: number;
+ endTime: number;
+ metric: Metric;
+ sort: Sort;
+ pagination: Pagination;
+ datasets?: string[];
+}
+
export const callGetMetricsK8sAnomaliesAPI = async (
- sourceId: string,
- startTime: number,
- endTime: number,
- metric: Metric,
- sort: Sort,
- pagination: Pagination,
- datasets?: string[]
+ requestArgs: RequestArgs,
+ fetch: HttpHandler
) => {
- const response = await npStart.http.fetch(INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, {
+ const { sourceId, startTime, endTime, metric, sort, pagination, datasets } = requestArgs;
+ const response = await fetch(INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, {
method: 'POST',
body: JSON.stringify(
getMetricsK8sAnomaliesRequestPayloadRT.encode({
diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts
index 3c6b1a14cfd47..0e49ca93010fd 100644
--- a/x-pack/plugins/infra/public/plugin.ts
+++ b/x-pack/plugins/infra/public/plugin.ts
@@ -9,7 +9,6 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
import { createMetricThresholdAlertType } from './alerting/metric_threshold';
import { createInventoryMetricAlertType } from './alerting/inventory';
import { getAlertType as getLogsAlertType } from './alerting/log_threshold';
-import { registerStartSingleton } from './legacy_singletons';
import { registerFeatures } from './register_feature';
import {
InfraClientSetupDeps,
@@ -98,9 +97,7 @@ export class Plugin implements InfraClientPluginClass {
});
}
- start(core: InfraClientCoreStart, _plugins: InfraClientStartDeps) {
- registerStartSingleton(core);
- }
+ start(_core: InfraClientCoreStart, _plugins: InfraClientStartDeps) {}
stop() {}
}
From e9fd3902c5a503083fa629e787d7deff1fc7a3bd Mon Sep 17 00:00:00 2001
From: Nick Partridge
Date: Fri, 2 Oct 2020 12:48:40 -0500
Subject: [PATCH 28/50] upgrade @elastic/charts to v23.0.0 (#79226)
Co-authored-by: Elastic Machine
---
package.json | 2 +-
packages/kbn-ui-shared-deps/package.json | 2 +-
.../lens/public/xy_visualization/expression.test.tsx | 2 +-
yarn.lock | 8 ++++----
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/package.json b/package.json
index ff98d7f85dcef..5089e6e1a140d 100644
--- a/package.json
+++ b/package.json
@@ -230,7 +230,7 @@
"@babel/parser": "^7.11.2",
"@babel/types": "^7.11.0",
"@elastic/apm-rum": "^5.6.1",
- "@elastic/charts": "21.1.2",
+ "@elastic/charts": "23.0.0",
"@elastic/ems-client": "7.10.0",
"@elastic/eslint-config-kibana": "0.15.0",
"@elastic/eslint-plugin-eui": "0.0.2",
diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json
index 278e8efd2d29e..e5f1a06e5bffa 100644
--- a/packages/kbn-ui-shared-deps/package.json
+++ b/packages/kbn-ui-shared-deps/package.json
@@ -9,7 +9,7 @@
"kbn:watch": "node scripts/build --dev --watch"
},
"dependencies": {
- "@elastic/charts": "21.1.2",
+ "@elastic/charts": "23.0.0",
"@elastic/eui": "29.0.0",
"@elastic/numeral": "^2.5.0",
"@kbn/i18n": "1.0.0",
diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
index 5fc89d831a961..405491ddc372a 100644
--- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
@@ -751,7 +751,7 @@ describe('xy_expression', () => {
});
test('onElementClick returns correct context data', () => {
- const geometry: GeometryValue = { x: 5, y: 1, accessor: 'y1', mark: null };
+ const geometry: GeometryValue = { x: 5, y: 1, accessor: 'y1', mark: null, datum: {} };
const series = {
key: 'spec{d}yAccessor{d}splitAccessors{b-2}',
specId: 'd',
diff --git a/yarn.lock b/yarn.lock
index 2d72b6d6c3bb6..971a94bfe56c3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1218,10 +1218,10 @@
dependencies:
"@elastic/apm-rum-core" "^5.7.0"
-"@elastic/charts@21.1.2":
- version "21.1.2"
- resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-21.1.2.tgz#da7e9c1025bf730a738b6ac6d7024d97dd2b5aa2"
- integrity sha512-Uri+Xolgii7/mRSarfXTfA6X2JC76ILIxTPO8RlYdI44gzprJfUO7Aw5s8vVQke3x6Cu39a+9B0s6TY4GAaApQ==
+"@elastic/charts@23.0.0":
+ version "23.0.0"
+ resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-23.0.0.tgz#6152f5ef0e31b2d7d7a5c95a2f7baba0f4296c18"
+ integrity sha512-FUZ72mzkIVYubtZMPA1bT7rrua1X2ZnsdzO+qRwR81QLfUYM6ht3WLaffVAUT1y6eXmnXFs+d88U7jlsontN6w==
dependencies:
"@popperjs/core" "^2.4.0"
chroma-js "^2.1.0"
From 85528d0ecd8422f9f855ee519ad94f55100f5149 Mon Sep 17 00:00:00 2001
From: Thomas Neirynck
Date: Fri, 2 Oct 2020 14:04:03 -0400
Subject: [PATCH 29/50] [Maps] Register gold+ feature use (#79011)
---
x-pack/plugins/maps/common/constants.ts | 1 +
.../maps/public/actions/layer_actions.test.ts | 51 ++++++++++++++++
.../maps/public/actions/layer_actions.ts | 8 ++-
.../blended_vector_layer.ts | 8 +++
.../maps/public/classes/layers/layer.tsx | 6 ++
.../layers/vector_layer/vector_layer.tsx | 4 ++
.../es_geo_grid_source/es_geo_grid_source.js | 9 +++
.../es_geo_grid_source.test.ts | 30 +++++++--
.../maps/public/classes/sources/source.ts | 6 ++
.../maps/public/index_pattern_util.test.ts | 5 +-
.../plugins/maps/public/index_pattern_util.ts | 3 +-
x-pack/plugins/maps/public/kibana_services.ts | 9 +--
.../plugins/maps/public/licensed_features.ts | 61 +++++++++++++++++++
x-pack/plugins/maps/public/meta.test.js | 6 +-
x-pack/plugins/maps/public/meta.ts | 2 +-
x-pack/plugins/maps/public/plugin.ts | 17 ++----
.../maps/public/selectors/map_selectors.ts | 6 +-
17 files changed, 197 insertions(+), 35 deletions(-)
create mode 100644 x-pack/plugins/maps/public/actions/layer_actions.test.ts
create mode 100644 x-pack/plugins/maps/public/licensed_features.ts
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index be891b6e59608..469a4023434a8 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -5,6 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
import { FeatureCollection } from 'geojson';
+
export const EMS_APP_NAME = 'kibana';
export const EMS_CATALOGUE_PATH = 'ems/catalogue';
diff --git a/x-pack/plugins/maps/public/actions/layer_actions.test.ts b/x-pack/plugins/maps/public/actions/layer_actions.test.ts
new file mode 100644
index 0000000000000..09a22dca271d7
--- /dev/null
+++ b/x-pack/plugins/maps/public/actions/layer_actions.test.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { addLayer } from './layer_actions';
+import { LayerDescriptor } from '../../common/descriptor_types';
+import { LICENSED_FEATURES } from '../licensed_features';
+
+const getStoreMock = jest.fn();
+const dispatchMock = jest.fn();
+
+describe('layer_actions', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('addLayer', () => {
+ const notifyLicensedFeatureUsageMock = jest.fn();
+
+ beforeEach(() => {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ require('../licensed_features').notifyLicensedFeatureUsage = (feature: LICENSED_FEATURES) => {
+ notifyLicensedFeatureUsageMock(feature);
+ };
+
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ require('../selectors/map_selectors').getMapReady = () => {
+ return true;
+ };
+
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ require('../selectors/map_selectors').createLayerInstance = () => {
+ return {
+ getLicensedFeatures() {
+ return [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE];
+ },
+ };
+ };
+ });
+
+ it('should register feature-use', async () => {
+ const action = addLayer(({} as unknown) as LayerDescriptor);
+ await action(dispatchMock, getStoreMock);
+ expect(notifyLicensedFeatureUsageMock).toHaveBeenCalledWith(
+ LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts
index 675bb14722889..19c9adfadd45a 100644
--- a/x-pack/plugins/maps/public/actions/layer_actions.ts
+++ b/x-pack/plugins/maps/public/actions/layer_actions.ts
@@ -14,6 +14,7 @@ import {
getSelectedLayerId,
getMapReady,
getMapColors,
+ createLayerInstance,
} from '../selectors/map_selectors';
import { FLYOUT_STATE } from '../reducers/ui';
import { cancelRequest } from '../reducers/non_serializable_instances';
@@ -42,6 +43,7 @@ import { ILayer } from '../classes/layers/layer';
import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer';
import { LAYER_STYLE_TYPE, LAYER_TYPE } from '../../common/constants';
import { IVectorStyle } from '../classes/styles/vector/vector_style';
+import { notifyLicensedFeatureUsage } from '../licensed_features';
export function trackCurrentLayerState(layerId: string) {
return {
@@ -108,7 +110,7 @@ export function cloneLayer(layerId: string) {
}
export function addLayer(layerDescriptor: LayerDescriptor) {
- return (dispatch: Dispatch, getState: () => MapStoreState) => {
+ return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const isMapReady = getMapReady(getState());
if (!isMapReady) {
dispatch({
@@ -123,6 +125,10 @@ export function addLayer(layerDescriptor: LayerDescriptor) {
layer: layerDescriptor,
});
dispatch(syncDataForLayerId(layerDescriptor.id));
+
+ const layer = createLayerInstance(layerDescriptor);
+ const features = await layer.getLicensedFeatures();
+ features.forEach(notifyLicensedFeatureUsage);
};
}
diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts
index 9b6a67ac28ad0..65a76f0c54ffb 100644
--- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts
+++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts
@@ -38,6 +38,7 @@ import {
VectorLayerDescriptor,
} from '../../../../common/descriptor_types';
import { IVectorSource } from '../../sources/vector_source';
+import { LICENSED_FEATURES } from '../../../licensed_features';
const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID';
@@ -327,4 +328,11 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
super._syncData(syncContext, activeSource, activeStyle);
}
+
+ async getLicensedFeatures(): Promise {
+ return [
+ ...(await this._clusterSource.getLicensedFeatures()),
+ ...(await this._documentSource.getLicensedFeatures()),
+ ];
+ }
}
diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx
index d6bd5180375ce..d7fd5d34a9dd0 100644
--- a/x-pack/plugins/maps/public/classes/layers/layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx
@@ -34,6 +34,7 @@ import { Attribution, ImmutableSourceProperty, ISource, SourceEditorArgs } from
import { DataRequestContext } from '../../actions';
import { IStyle } from '../styles/style';
import { getJoinAggKey } from '../../../common/get_agg_key';
+import { LICENSED_FEATURES } from '../../licensed_features';
export interface ILayer {
getBounds(dataRequestContext: DataRequestContext): Promise;
@@ -91,6 +92,7 @@ export interface ILayer {
showJoinEditor(): boolean;
getJoinsDisabledReason(): string | null;
isFittable(): Promise;
+ getLicensedFeatures(): Promise;
}
export type Footnote = {
icon: ReactElement;
@@ -538,4 +540,8 @@ export class AbstractLayer implements ILayer {
supportsLabelsOnTop(): boolean {
return false;
}
+
+ async getLicensedFeatures(): Promise {
+ return [];
+ }
}
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
index a2532d4e7b10e..c44ebcf969f7c 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
@@ -1090,4 +1090,8 @@ export class VectorLayer extends AbstractLayer {
});
return targetFeature ? targetFeature : null;
}
+
+ async getLicensedFeatures() {
+ return await this._source.getLicensedFeatures();
+ }
}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js
index 89258f04612fd..181af6b17b7dd 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js
@@ -24,12 +24,14 @@ import {
MVT_GETGRIDTILE_API_PATH,
GEOTILE_GRID_AGG_NAME,
GEOCENTROID_AGG_NAME,
+ ES_GEO_FIELD_TYPE,
} from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source';
import { DataRequestAbortError } from '../../util/data_request';
import { registerSource } from '../source_registry';
+import { LICENSED_FEATURES } from '../../../licensed_features';
import rison from 'rison-node';
import { getHttp } from '../../../kibana_services';
@@ -399,6 +401,13 @@ export class ESGeoGridSource extends AbstractESAggSource {
return [VECTOR_SHAPE_TYPE.POINT];
}
+
+ async getLicensedFeatures() {
+ const geoField = await this._getGeoField();
+ return geoField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE
+ ? [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE]
+ : [];
+ }
}
registerSource({
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
index 06df68283c434..3b1cf3293c0d3 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
@@ -4,10 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { MapExtent, VectorSourceRequestMeta } from '../../../../common/descriptor_types';
-
-jest.mock('../../../kibana_services');
-
-import { getIndexPatternService, getSearchService, getHttp } from '../../../kibana_services';
+import { getHttp, getIndexPatternService, getSearchService } from '../../../kibana_services';
import { ESGeoGridSource } from './es_geo_grid_source';
import {
ES_GEO_FIELD_TYPE,
@@ -16,6 +13,9 @@ import {
SOURCE_TYPES,
} from '../../../../common/constants';
import { SearchSource } from 'src/plugins/data/public';
+import { LICENSED_FEATURES } from '../../../licensed_features';
+
+jest.mock('../../../kibana_services');
export class MockSearchSource {
setField = jest.fn();
@@ -27,6 +27,8 @@ export class MockSearchSource {
describe('ESGeoGridSource', () => {
const geoFieldName = 'bar';
+
+ let esGeoFieldType = ES_GEO_FIELD_TYPE.GEO_POINT;
const mockIndexPatternService = {
get() {
return {
@@ -34,7 +36,7 @@ describe('ESGeoGridSource', () => {
getByName() {
return {
name: geoFieldName,
- type: ES_GEO_FIELD_TYPE.GEO_POINT,
+ type: esGeoFieldType,
};
},
},
@@ -127,6 +129,11 @@ describe('ESGeoGridSource', () => {
});
});
+ afterEach(() => {
+ esGeoFieldType = ES_GEO_FIELD_TYPE.GEO_POINT;
+ jest.resetAllMocks();
+ });
+
const extent: MapExtent = {
minLon: -160,
minLat: -80,
@@ -271,4 +278,17 @@ describe('ESGeoGridSource', () => {
);
});
});
+
+ describe('Gold+ usage', () => {
+ it('Should have none for points', async () => {
+ expect(await geogridSource.getLicensedFeatures()).toEqual([]);
+ });
+
+ it('Should have shape-aggs for geo_shape', async () => {
+ esGeoFieldType = ES_GEO_FIELD_TYPE.GEO_SHAPE;
+ expect(await geogridSource.getLicensedFeatures()).toEqual([
+ LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE,
+ ]);
+ });
+ });
});
diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts
index 946381817b8fc..c4fb5178c0b56 100644
--- a/x-pack/plugins/maps/public/classes/sources/source.ts
+++ b/x-pack/plugins/maps/public/classes/sources/source.ts
@@ -15,6 +15,7 @@ import { IField } from '../fields/field';
import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants';
import { AbstractSourceDescriptor } from '../../../common/descriptor_types';
import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view';
+import { LICENSED_FEATURES } from '../../licensed_features';
export type SourceEditorArgs = {
onChange: (...args: OnSourceChangeArgs[]) => void;
@@ -66,6 +67,7 @@ export interface ISource {
getValueSuggestions(field: IField, query: string): Promise;
getMinZoom(): number;
getMaxZoom(): number;
+ getLicensedFeatures(): Promise;
}
export class AbstractSource implements ISource {
@@ -188,4 +190,8 @@ export class AbstractSource implements ISource {
getMaxZoom() {
return MAX_ZOOM;
}
+
+ async getLicensedFeatures(): Promise {
+ return [];
+ }
}
diff --git a/x-pack/plugins/maps/public/index_pattern_util.test.ts b/x-pack/plugins/maps/public/index_pattern_util.test.ts
index ffcc6da52677a..010c847f96eba 100644
--- a/x-pack/plugins/maps/public/index_pattern_util.test.ts
+++ b/x-pack/plugins/maps/public/index_pattern_util.test.ts
@@ -5,6 +5,7 @@
*/
jest.mock('./kibana_services', () => ({}));
+jest.mock('./licensed_features', () => ({}));
import {
getSourceFields,
@@ -69,7 +70,7 @@ describe('Gold+ licensing', () => {
describe('basic license', () => {
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
- require('./kibana_services').getIsGoldPlus = () => false;
+ require('./licensed_features').getIsGoldPlus = () => false;
});
describe('getAggregatableGeoFieldTypes', () => {
@@ -92,7 +93,7 @@ describe('Gold+ licensing', () => {
describe('gold license', () => {
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
- require('./kibana_services').getIsGoldPlus = () => true;
+ require('./licensed_features').getIsGoldPlus = () => true;
});
describe('getAggregatableGeoFieldTypes', () => {
test('Should add geo_shape field', () => {
diff --git a/x-pack/plugins/maps/public/index_pattern_util.ts b/x-pack/plugins/maps/public/index_pattern_util.ts
index bd2a14619ac41..7af1571a0bc5b 100644
--- a/x-pack/plugins/maps/public/index_pattern_util.ts
+++ b/x-pack/plugins/maps/public/index_pattern_util.ts
@@ -6,9 +6,10 @@
import { IFieldType, IndexPattern } from 'src/plugins/data/public';
import { i18n } from '@kbn/i18n';
-import { getIndexPatternService, getIsGoldPlus } from './kibana_services';
+import { getIndexPatternService } from './kibana_services';
import { indexPatterns } from '../../../../src/plugins/data/public';
import { ES_GEO_FIELD_TYPE, ES_GEO_FIELD_TYPES } from '../common/constants';
+import { getIsGoldPlus } from './licensed_features';
export function getGeoTileAggNotSupportedReason(field: IFieldType): string | null {
if (!field.aggregatable) {
diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts
index c1dfb61e9f3b6..b520e0cb2df01 100644
--- a/x-pack/plugins/maps/public/kibana_services.ts
+++ b/x-pack/plugins/maps/public/kibana_services.ts
@@ -5,17 +5,10 @@
*/
import _ from 'lodash';
+import { CoreStart } from 'kibana/public';
import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config';
import { MapsConfigType } from '../config';
import { MapsPluginStartDependencies } from './plugin';
-import { CoreStart } from '../../../../src/core/public';
-
-let licenseId: string | undefined;
-export const setLicenseId = (latestLicenseId: string | undefined) => (licenseId = latestLicenseId);
-export const getLicenseId = () => licenseId;
-let isGoldPlus: boolean = false;
-export const setIsGoldPlus = (igp: boolean) => (isGoldPlus = igp);
-export const getIsGoldPlus = () => isGoldPlus;
let kibanaVersion: string;
export const setKibanaVersion = (version: string) => (kibanaVersion = version);
diff --git a/x-pack/plugins/maps/public/licensed_features.ts b/x-pack/plugins/maps/public/licensed_features.ts
new file mode 100644
index 0000000000000..67fa526da0cbd
--- /dev/null
+++ b/x-pack/plugins/maps/public/licensed_features.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ILicense, LicenseType } from '../../licensing/common/types';
+import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public';
+import { APP_ID } from '../common/constants';
+
+export enum LICENSED_FEATURES {
+ GEO_SHAPE_AGGS_GEO_TILE = 'GEO_SHAPE_AGGS_GEO_TILE',
+}
+
+export interface LicensedFeatureDetail {
+ name: string;
+ license: LicenseType;
+}
+
+export const LICENCED_FEATURES_DETAILS: Record = {
+ [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE]: {
+ name: 'geo_tile aggregation on geo_shape field-type',
+ license: 'gold',
+ },
+};
+
+let licenseId: string | undefined;
+let isGoldPlus: boolean = false;
+
+export const getLicenseId = () => licenseId;
+export const getIsGoldPlus = () => isGoldPlus;
+
+export function registerLicensedFeatures(licensingPlugin: LicensingPluginSetup) {
+ for (const licensedFeature of Object.values(LICENSED_FEATURES)) {
+ licensingPlugin.featureUsage.register(
+ LICENCED_FEATURES_DETAILS[licensedFeature].name,
+ LICENCED_FEATURES_DETAILS[licensedFeature].license
+ );
+ }
+}
+
+let licensingPluginStart: LicensingPluginStart;
+export function setLicensingPluginStart(licensingPlugin: LicensingPluginStart) {
+ licensingPluginStart = licensingPlugin;
+ licensingPluginStart.license$.subscribe((license: ILicense) => {
+ const gold = license.check(APP_ID, 'gold');
+ isGoldPlus = gold.state === 'valid';
+ licenseId = license.uid;
+ });
+}
+
+export function notifyLicensedFeatureUsage(licensedFeature: LICENSED_FEATURES) {
+ if (!licensingPluginStart) {
+ // eslint-disable-next-line no-console
+ console.error('May not call notifyLicensedFeatureUsage before plugin start');
+ return;
+ }
+ licensingPluginStart.featureUsage.notifyUsage(
+ LICENCED_FEATURES_DETAILS[LICENSED_FEATURES[licensedFeature]].name
+ );
+}
diff --git a/x-pack/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js
index 3486bf003aee0..c414c8a2d400e 100644
--- a/x-pack/plugins/maps/public/meta.test.js
+++ b/x-pack/plugins/maps/public/meta.test.js
@@ -12,14 +12,14 @@ jest.mock('@elastic/ems-client');
describe('default use without proxy', () => {
beforeEach(() => {
require('./kibana_services').getProxyElasticMapsServiceInMaps = () => false;
- require('./kibana_services').getLicenseId = () => {
- return 'foobarlicenseid';
- };
require('./kibana_services').getIsEmsEnabled = () => true;
require('./kibana_services').getEmsTileLayerId = () => '123';
require('./kibana_services').getEmsFileApiUrl = () => 'https://file-api';
require('./kibana_services').getEmsTileApiUrl = () => 'https://tile-api';
require('./kibana_services').getEmsLandingPageUrl = () => 'http://test.com';
+ require('./licensed_features').getLicenseId = () => {
+ return 'foobarlicenseid';
+ };
});
test('should construct EMSClient with absolute file and tile API urls', async () => {
diff --git a/x-pack/plugins/maps/public/meta.ts b/x-pack/plugins/maps/public/meta.ts
index 5142793bede34..4eca6c3e671b7 100644
--- a/x-pack/plugins/maps/public/meta.ts
+++ b/x-pack/plugins/maps/public/meta.ts
@@ -18,7 +18,6 @@ import {
} from '../common/constants';
import {
getHttp,
- getLicenseId,
getIsEmsEnabled,
getRegionmapLayers,
getTilemap,
@@ -29,6 +28,7 @@ import {
getProxyElasticMapsServiceInMaps,
getKibanaVersion,
} from './kibana_services';
+import { getLicenseId } from './licensed_features';
export function getKibanaRegionList(): unknown[] {
return getRegionmapLayers();
diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts
index 696964f0258d4..5b79863d0dd97 100644
--- a/x-pack/plugins/maps/public/plugin.ts
+++ b/x-pack/plugins/maps/public/plugin.ts
@@ -18,10 +18,8 @@ import {
// @ts-ignore
import { MapView } from './inspector/views/map_view';
import {
- setIsGoldPlus,
setKibanaCommonConfig,
setKibanaVersion,
- setLicenseId,
setMapAppConfig,
setStartServices,
} from './kibana_services';
@@ -42,7 +40,6 @@ import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory';
import { EmbeddableSetup } from '../../../../src/plugins/embeddable/public';
import { MapsXPackConfig, MapsConfigType } from '../config';
import { getAppTitle } from '../common/i18n_getters';
-import { ILicense } from '../../licensing/common/types';
import { lazyLoadMapModules } from './lazy_load_bundle';
import { MapsStartApi } from './api';
import { createSecurityLayerDescriptors, registerLayerWizard, registerSource } from './api';
@@ -50,8 +47,9 @@ import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/shar
import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
-import { LicensingPluginStart } from '../../licensing/public';
+import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public';
import { StartContract as FileUploadStartContract } from '../../file_upload/public';
+import { registerLicensedFeatures, setLicensingPluginStart } from './licensed_features';
export interface MapsPluginSetupDependencies {
inspector: InspectorSetupContract;
@@ -60,6 +58,7 @@ export interface MapsPluginSetupDependencies {
embeddable: EmbeddableSetup;
mapsLegacy: { config: MapsLegacyConfig };
share: SharePluginSetup;
+ licensing: LicensingPluginSetup;
}
export interface MapsPluginStartDependencies {
@@ -97,6 +96,8 @@ export class MapsPlugin
}
public setup(core: CoreSetup, plugins: MapsPluginSetupDependencies) {
+ registerLicensedFeatures(plugins.licensing);
+
const config = this._initializerContext.config.get();
setKibanaCommonConfig(plugins.mapsLegacy.config);
setMapAppConfig(config);
@@ -138,13 +139,7 @@ export class MapsPlugin
}
public start(core: CoreStart, plugins: MapsPluginStartDependencies): MapsStartApi {
- if (plugins.licensing) {
- plugins.licensing.license$.subscribe((license: ILicense) => {
- const gold = license.check(APP_ID, 'gold');
- setIsGoldPlus(gold.state === 'valid');
- setLicenseId(license.uid);
- });
- }
+ setLicensingPluginStart(plugins.licensing);
plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction);
setStartServices(core, plugins);
diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts
index db4371e9cd590..4b5122050eb71 100644
--- a/x-pack/plugins/maps/public/selectors/map_selectors.ts
+++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts
@@ -52,9 +52,9 @@ import { ITMSSource } from '../classes/sources/tms_source';
import { IVectorSource } from '../classes/sources/vector_source';
import { ILayer } from '../classes/layers/layer';
-function createLayerInstance(
+export function createLayerInstance(
layerDescriptor: LayerDescriptor,
- inspectorAdapters: Adapters
+ inspectorAdapters?: Adapters
): ILayer {
const source: ISource = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters);
@@ -94,7 +94,7 @@ function createLayerInstance(
}
}
-function createSourceInstance(sourceDescriptor: any, inspectorAdapters: Adapters): ISource {
+function createSourceInstance(sourceDescriptor: any, inspectorAdapters?: Adapters): ISource {
const source = getSourceByType(sourceDescriptor.type);
if (!source) {
throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`);
From 86cb97adf6b1692277695fab7089f4a559346cc2 Mon Sep 17 00:00:00 2001
From: Thomas Neirynck
Date: Fri, 2 Oct 2020 14:04:31 -0400
Subject: [PATCH 30/50] [Maps] Simplify IDynamicStyle-api (#79217)
---
.../properties/dynamic_style_property.tsx | 54 ++++++++++++++-----
.../classes/styles/vector/vector_style.tsx | 40 +++++---------
2 files changed, 54 insertions(+), 40 deletions(-)
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx
index 2bc819daeea90..98b58def905eb 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx
@@ -6,7 +6,8 @@
import _ from 'lodash';
import React from 'react';
-import { Feature } from 'geojson';
+import { Feature, FeatureCollection } from 'geojson';
+import { FeatureIdentifier, Map as MbMap } from 'mapbox-gl';
import { AbstractStyleProperty, IStyleProperty } from './style_property';
import { DEFAULT_SIGMA } from '../vector_style_defaults';
import {
@@ -44,20 +45,14 @@ export interface IDynamicStyleProperty extends IStyleProperty {
isOrdinal(): boolean;
supportsFieldMeta(): boolean;
getFieldMetaRequest(): Promise;
- supportsMbFeatureState(): boolean;
- getMbLookupFunction(): MB_LOOKUP_FUNCTION;
pluckOrdinalStyleMetaFromFeatures(features: Feature[]): RangeFieldMeta | null;
pluckCategoricalStyleMetaFromFeatures(features: Feature[]): CategoryFieldMeta | null;
getValueSuggestions(query: string): Promise;
-
- // Returns the name that should be used for accessing the data from the mb-style rule
- // Depending on
- // - whether the field is used for labeling, icon-orientation, or other properties (color, size, ...), `feature-state` and or `get` is used
- // - whether the field was run through a field-formatter, a new dynamic field is created with the formatted-value
- // The combination of both will inform what field-name (e.g. the "raw" field name from the properties, the "computed field-name" for an on-the-fly created property (e.g. for feature-state or field-formatting).
- // todo: There is an existing limitation to .mvt backed sources, where the field-formatters are not applied. Here, the raw-data needs to be accessed.
- getMbPropertyName(): string;
- getMbPropertyValue(value: RawValue): RawValue;
+ enrichGeoJsonAndMbFeatureState(
+ featureCollection: FeatureCollection,
+ mbMap: MbMap,
+ mbSourceId: string
+ ): boolean;
}
export class DynamicStyleProperty
@@ -356,6 +351,12 @@ export class DynamicStyleProperty
);
}
+ // Returns the name that should be used for accessing the data from the mb-style rule
+ // Depending on
+ // - whether the field is used for labeling, icon-orientation, or other properties (color, size, ...), `feature-state` and or `get` is used
+ // - whether the field was run through a field-formatter, a new dynamic field is created with the formatted-value
+ // The combination of both will inform what field-name (e.g. the "raw" field name from the properties, the "computed field-name" for an on-the-fly created property (e.g. for feature-state or field-formatting).
+ // todo: There is an existing limitation to .mvt backed sources, where the field-formatters are not applied. Here, the raw-data needs to be accessed.
getMbPropertyName() {
if (!this._field) {
return '';
@@ -385,6 +386,35 @@ export class DynamicStyleProperty
// Calling `isOrdinal` would be equivalent.
return this.supportsMbFeatureState() ? getNumericalMbFeatureStateValue(rawValue) : rawValue;
}
+
+ enrichGeoJsonAndMbFeatureState(
+ featureCollection: FeatureCollection,
+ mbMap: MbMap,
+ mbSourceId: string
+ ): boolean {
+ const supportsFeatureState = this.supportsMbFeatureState();
+ const featureIdentifier: FeatureIdentifier = {
+ source: mbSourceId,
+ id: undefined,
+ };
+ const featureState: Record = {};
+ const targetMbName = this.getMbPropertyName();
+ for (let i = 0; i < featureCollection.features.length; i++) {
+ const feature = featureCollection.features[i];
+ const rawValue = feature.properties ? feature.properties[this.getFieldName()] : undefined;
+ const targetMbValue = this.getMbPropertyValue(rawValue);
+ if (supportsFeatureState) {
+ featureState[targetMbName] = targetMbValue; // the same value will be potentially overridden multiple times, if the name remains identical
+ featureIdentifier.id = feature.id;
+ mbMap.setFeatureState(featureIdentifier, featureState);
+ } else {
+ if (feature.properties) {
+ feature.properties[targetMbName] = targetMbValue;
+ }
+ }
+ }
+ return supportsFeatureState;
+ }
}
export function getNumericalMbFeatureStateValue(value: RawValue) {
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx
index 5d0d9712ef988..acb158636e0b3 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx
@@ -641,7 +641,7 @@ export class VectorStyle implements IVectorStyle {
featureCollection: FeatureCollection,
mbMap: MbMap,
mbSourceId: string
- ) {
+ ): boolean {
if (!featureCollection) {
return false;
}
@@ -651,40 +651,24 @@ export class VectorStyle implements IVectorStyle {
return false;
}
- const tmpFeatureIdentifier: FeatureIdentifier = {
- source: '',
- id: undefined,
- };
- const tmpFeatureState: any = {};
-
- for (let i = 0; i < featureCollection.features.length; i++) {
- const feature = featureCollection.features[i];
-
- for (let j = 0; j < dynamicStyleProps.length; j++) {
- const dynamicStyleProp = dynamicStyleProps[j];
- const targetMbName = dynamicStyleProp.getMbPropertyName();
- const rawValue = feature.properties
- ? feature.properties[dynamicStyleProp.getFieldName()]
- : undefined;
- const targetMbValue = dynamicStyleProp.getMbPropertyValue(rawValue);
- if (dynamicStyleProp.supportsMbFeatureState()) {
- tmpFeatureState[targetMbName] = targetMbValue; // the same value will be potentially overridden multiple times, if the name remains identical
- } else {
- if (feature.properties) {
- feature.properties[targetMbName] = targetMbValue;
- }
- }
+ let shouldResetAllData = false;
+ for (let j = 0; j < dynamicStyleProps.length; j++) {
+ const dynamicStyleProp = dynamicStyleProps[j];
+ const usedFeatureState = dynamicStyleProp.enrichGeoJsonAndMbFeatureState(
+ featureCollection,
+ mbMap,
+ mbSourceId
+ );
+ if (!usedFeatureState) {
+ shouldResetAllData = true;
}
- tmpFeatureIdentifier.source = mbSourceId;
- tmpFeatureIdentifier.id = feature.id;
- mbMap.setFeatureState(tmpFeatureIdentifier, tmpFeatureState);
}
// returns boolean indicating if styles do not support feature-state and some values are stored in geojson properties
// this return-value is used in an optimization for style-updates with mapbox-gl.
// `true` indicates the entire data needs to reset on the source (otherwise the style-rules will not be reapplied)
// `false` indicates the data does not need to be reset on the store, because styles are re-evaluated if they use featureState
- return dynamicStyleProps.some((dynamicStyleProp) => !dynamicStyleProp.supportsMbFeatureState());
+ return shouldResetAllData;
}
arePointsSymbolizedAsCircles() {
From 819ccf124703f5da04bbe73e1a892b6a96e1c3e7 Mon Sep 17 00:00:00 2001
From: Robert Oskamp
Date: Fri, 2 Oct 2020 20:14:42 +0200
Subject: [PATCH 31/50] Adjust extend_es_archiver to handle additional cases
(#79308)
This PR enables the extend_es_archiver to recognize additional indices like .kibana_1 as well as a list of indices.
---
test/common/services/kibana_server/extend_es_archiver.js | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/test/common/services/kibana_server/extend_es_archiver.js b/test/common/services/kibana_server/extend_es_archiver.js
index 4efdfc4dddf77..f6e14061aed2a 100644
--- a/test/common/services/kibana_server/extend_es_archiver.js
+++ b/test/common/services/kibana_server/extend_es_archiver.js
@@ -33,9 +33,15 @@ export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults })
// esArchiver methods return a stats object, with information about the indexes created
const stats = await originalMethod.apply(esArchiver, args);
+ const statsKeys = Object.keys(stats);
+ const kibanaKeys = statsKeys.filter(
+ // this also matches stats keys like '.kibana_1' and '.kibana_2,.kibana_1'
+ (key) => key.includes(KIBANA_INDEX) && (stats[key].created || stats[key].deleted)
+ );
+
// if the kibana index was created by the esArchiver then update the uiSettings
// with the defaults to make sure that they are always in place initially
- if (stats[KIBANA_INDEX] && (stats[KIBANA_INDEX].created || stats[KIBANA_INDEX].deleted)) {
+ if (kibanaKeys.length > 0) {
await retry.try(async () => {
await kibanaServer.uiSettings.update(defaults);
});
From 92ff5178c6ee0b226c9e89aff00e3b3e8dcc7562 Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Fri, 2 Oct 2020 14:15:03 -0400
Subject: [PATCH 32/50] [Security Solution][EPM] Reenabling the ingest and
endpoint tests (#79290)
* Reenabling the ingest and endpoint tests
* Fixing list test and reenabling security functional tests
---
.../apis/epm/list.ts | 28 ++++++++-----------
.../apis/index.js | 2 +-
.../ingest_manager_api_integration/config.ts | 2 +-
.../apps/endpoint/index.ts | 2 +-
.../apis/index.ts | 2 +-
5 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts
index bfe1954e46c9f..732696433279f 100644
--- a/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts
+++ b/x-pack/test/ingest_manager_api_integration/apis/epm/list.ts
@@ -6,21 +6,23 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
-import { warnAndSkipTest } from '../../helpers';
+import { skipIfNoDockerRegistry } from '../../helpers';
+import { setupIngest } from '../fleet/agents/services';
-export default function ({ getService }: FtrProviderContext) {
- const log = getService('log');
+export default function (providerContext: FtrProviderContext) {
+ const { getService } = providerContext;
const supertest = getService('supertest');
- const dockerServers = getService('dockerServers');
- const server = dockerServers.get('registry');
// use function () {} and not () => {} here
// because `this` has to point to the Mocha context
// see https://mochajs.org/#arrow-functions
describe('EPM - list', async function () {
- it('lists all packages from the registry', async function () {
- if (server.enabled) {
+ skipIfNoDockerRegistry(providerContext);
+ setupIngest(providerContext);
+
+ describe('list api tests', async () => {
+ it('lists all packages from the registry', async function () {
const fetchPackageList = async () => {
const response = await supertest
.get('/api/ingest_manager/epm/packages')
@@ -30,13 +32,9 @@ export default function ({ getService }: FtrProviderContext) {
};
const listResponse = await fetchPackageList();
expect(listResponse.response.length).not.to.be(0);
- } else {
- warnAndSkipTest(this, log);
- }
- });
+ });
- it('lists all limited packages from the registry', async function () {
- if (server.enabled) {
+ it('lists all limited packages from the registry', async function () {
const fetchLimitedPackageList = async () => {
const response = await supertest
.get('/api/ingest_manager/epm/packages/limited')
@@ -46,9 +44,7 @@ export default function ({ getService }: FtrProviderContext) {
};
const listResponse = await fetchLimitedPackageList();
expect(listResponse.response).to.eql(['endpoint']);
- } else {
- warnAndSkipTest(this, log);
- }
+ });
});
});
}
diff --git a/x-pack/test/ingest_manager_api_integration/apis/index.js b/x-pack/test/ingest_manager_api_integration/apis/index.js
index ec509e1485a9f..7c1ebef337baa 100644
--- a/x-pack/test/ingest_manager_api_integration/apis/index.js
+++ b/x-pack/test/ingest_manager_api_integration/apis/index.js
@@ -5,7 +5,7 @@
*/
export default function ({ loadTestFile }) {
- describe.skip('Ingest Manager Endpoints', function () {
+ describe('Ingest Manager Endpoints', function () {
this.tags('ciGroup7');
// Ingest Manager setup
loadTestFile(require.resolve('./setup'));
diff --git a/x-pack/test/ingest_manager_api_integration/config.ts b/x-pack/test/ingest_manager_api_integration/config.ts
index 862ef732cb8b4..d11884667c48a 100644
--- a/x-pack/test/ingest_manager_api_integration/config.ts
+++ b/x-pack/test/ingest_manager_api_integration/config.ts
@@ -12,7 +12,7 @@ import { defineDockerServersConfig } from '@kbn/test';
// Docker image to use for Ingest Manager API integration tests.
// This hash comes from the commit hash here: https://github.com/elastic/package-storage/commit
export const dockerImage =
- 'docker.elastic.co/package-registry/distribution:518a65a993ab7e9c77b1d8d20cd6f847921d04ec';
+ 'docker.elastic.co/package-registry/distribution:a5132271ad37209d6978018bfe6e224546d719a8';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts
index c3862d130202d..654aa18fba523 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts
@@ -13,7 +13,7 @@ import {
export default function (providerContext: FtrProviderContext) {
const { loadTestFile, getService } = providerContext;
- describe.skip('endpoint', function () {
+ describe('endpoint', function () {
this.tags('ciGroup7');
const ingestManager = getService('ingestManager');
const log = getService('log');
diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts
index 6c5764faed631..3d344c1b3b51b 100644
--- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts
+++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts
@@ -10,7 +10,7 @@ import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../plugins/ing
export default function endpointAPIIntegrationTests(providerContext: FtrProviderContext) {
const { loadTestFile, getService } = providerContext;
- describe.skip('Endpoint plugin', function () {
+ describe('Endpoint plugin', function () {
const ingestManager = getService('ingestManager');
this.tags('ciGroup7');
From 374ccfd66fcbfbbde29dd080bf805d58b7dc9d7a Mon Sep 17 00:00:00 2001
From: Wylie Conlon
Date: Fri, 2 Oct 2020 14:18:01 -0400
Subject: [PATCH 33/50] [Lens] Show runtime fields in field list and improve
performance (#79167)
* [Lens] Simplify request to determine existing fields
* Remove duplicate values
---
.../server/routes/existing_fields.test.ts | 131 ++--------------
.../lens/server/routes/existing_fields.ts | 142 ++----------------
.../apis/lens/existing_fields.ts | 5 +-
.../logstash_functional/mappings.json | 15 ++
.../es_archives/visualize/default/data.json | 2 +-
5 files changed, 47 insertions(+), 248 deletions(-)
diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts
index 9799dcf92ae41..c877e69d7b0dd 100644
--- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts
+++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts
@@ -14,99 +14,55 @@ describe('existingFields', () => {
return {
name,
isScript: false,
- isAlias: false,
isMeta: false,
- path: name.split('.'),
...obj,
};
}
- function indexPattern(_source: unknown, fields: unknown = {}) {
- return { _source, fields };
+ function searchResults(fields: Record = {}) {
+ return { fields };
}
it('should handle root level fields', () => {
const result = existingFields(
- [indexPattern({ foo: 'bar' }), indexPattern({ baz: 0 })],
+ [searchResults({ foo: ['bar'] }), searchResults({ baz: [0] })],
[field('foo'), field('bar'), field('baz')]
);
expect(result).toEqual(['foo', 'baz']);
});
- it('should handle arrays of objects', () => {
+ it('should handle basic arrays, ignoring empty ones', () => {
const result = existingFields(
- [indexPattern({ stuff: [{ foo: 'bar' }, { baz: 0 }] })],
- [field('stuff.foo'), field('stuff.bar'), field('stuff.baz')]
+ [searchResults({ stuff: ['heyo', 'there'], empty: [] })],
+ [field('stuff'), field('empty')]
);
- expect(result).toEqual(['stuff.foo', 'stuff.baz']);
- });
-
- it('should handle basic arrays', () => {
- const result = existingFields([indexPattern({ stuff: ['heyo', 'there'] })], [field('stuff')]);
-
expect(result).toEqual(['stuff']);
});
- it('should handle deep object structures', () => {
- const result = existingFields(
- [indexPattern({ geo: { coordinates: { lat: 40, lon: -77 } } })],
- [field('geo.coordinates')]
- );
-
- expect(result).toEqual(['geo.coordinates']);
- });
-
it('should handle objects with dotted fields', () => {
const result = existingFields(
- [indexPattern({ 'geo.country_name': 'US' })],
+ [searchResults({ 'geo.country_name': ['US'] })],
[field('geo.country_name')]
);
expect(result).toEqual(['geo.country_name']);
});
- it('should handle arrays with dotted fields on both sides', () => {
- const result = existingFields(
- [indexPattern({ 'process.cpu': [{ 'user.pct': 50 }] })],
- [field('process.cpu.user.pct')]
- );
-
- expect(result).toEqual(['process.cpu.user.pct']);
- });
-
- it('should be false if it hits a positive leaf before the end of the path', () => {
- const result = existingFields(
- [indexPattern({ geo: { coordinates: 32 } })],
- [field('geo.coordinates.lat')]
- );
-
- expect(result).toEqual([]);
- });
-
- it('should use path, not name', () => {
- const result = existingFields(
- [indexPattern({ stuff: [{ foo: 'bar' }, { baz: 0 }] })],
- [field({ name: 'goober', path: ['stuff', 'foo'] })]
- );
-
- expect(result).toEqual(['goober']);
- });
-
it('supports scripted fields', () => {
const result = existingFields(
- [indexPattern({}, { bar: 'scriptvalue' })],
- [field({ name: 'baz', isScript: true, path: ['bar'] })]
+ [searchResults({ bar: ['scriptvalue'] })],
+ [field({ name: 'bar', isScript: true })]
);
- expect(result).toEqual(['baz']);
+ expect(result).toEqual(['bar']);
});
it('supports meta fields', () => {
const result = existingFields(
- [{ _mymeta: 'abc', ...indexPattern({}, { bar: 'scriptvalue' }) }],
- [field({ name: '_mymeta', isMeta: true, path: ['_mymeta'] })]
+ [{ _mymeta: 'abc', ...searchResults({ bar: ['scriptvalue'] }) }],
+ [field({ name: '_mymeta', isMeta: true })]
);
expect(result).toEqual(['_mymeta']);
@@ -132,81 +88,22 @@ describe('buildFieldList', () => {
references: [],
};
- const mappings = {
- testpattern: {
- mappings: {
- properties: {
- '@bar': {
- type: 'alias',
- path: 'bar',
- },
- },
- },
- },
- };
-
- const fieldDescriptors = [
- {
- name: 'baz',
- subType: { multi: { parent: 'a.b.c' } },
- },
- ];
-
- it('uses field descriptors to determine the path', () => {
- const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, []);
- expect(fields.find((f) => f.name === 'baz')).toMatchObject({
- isAlias: false,
- isScript: false,
- name: 'baz',
- path: ['a', 'b', 'c'],
- });
- });
-
- it('uses aliases to determine the path', () => {
- const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, []);
- expect(fields.find((f) => f.isAlias)).toMatchObject({
- isAlias: true,
- isScript: false,
- name: '@bar',
- path: ['bar'],
- });
- });
-
it('supports scripted fields', () => {
- const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, []);
+ const fields = buildFieldList(indexPattern, []);
expect(fields.find((f) => f.isScript)).toMatchObject({
- isAlias: false,
isScript: true,
name: 'foo',
- path: ['foo'],
lang: 'painless',
script: '2+2',
});
});
it('supports meta fields', () => {
- const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, ['_mymeta']);
+ const fields = buildFieldList(indexPattern, ['_mymeta']);
expect(fields.find((f) => f.isMeta)).toMatchObject({
- isAlias: false,
isScript: false,
isMeta: true,
name: '_mymeta',
- path: ['_mymeta'],
- });
- });
-
- it('handles missing mappings', () => {
- const fields = buildFieldList(indexPattern, {}, fieldDescriptors, []);
- expect(fields.every((f) => f.isAlias === false)).toEqual(true);
- });
-
- it('handles empty fieldDescriptors by skipping multi-mappings', () => {
- const fields = buildFieldList(indexPattern, mappings, [], []);
- expect(fields.find((f) => f.name === 'baz')).toMatchObject({
- isAlias: false,
- isScript: false,
- name: 'baz',
- path: ['baz'],
});
});
});
diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts
index 33fcafacfad73..c925517b572da 100644
--- a/x-pack/plugins/lens/server/routes/existing_fields.ts
+++ b/x-pack/plugins/lens/server/routes/existing_fields.ts
@@ -9,36 +9,17 @@ import { schema } from '@kbn/config-schema';
import { ILegacyScopedClusterClient, SavedObject, RequestHandlerContext } from 'src/core/server';
import { CoreSetup } from 'src/core/server';
import { BASE_API_URL } from '../../common';
-import {
- IndexPatternsFetcher,
- IndexPatternAttributes,
- UI_SETTINGS,
-} from '../../../../../src/plugins/data/server';
+import { IndexPatternAttributes, UI_SETTINGS } from '../../../../../src/plugins/data/server';
/**
* The number of docs to sample to determine field empty status.
*/
const SAMPLE_SIZE = 500;
-interface MappingResult {
- [indexPatternTitle: string]: {
- mappings: {
- properties: Record;
- };
- };
-}
-
-interface FieldDescriptor {
- name: string;
- subType?: { multi?: { parent?: string } };
-}
-
export interface Field {
name: string;
isScript: boolean;
- isAlias: boolean;
isMeta: boolean;
- path: string[];
lang?: string;
script?: string;
}
@@ -105,14 +86,12 @@ async function fetchFieldExistence({
timeFieldName?: string;
}) {
const metaFields: string[] = await context.core.uiSettings.client.get(UI_SETTINGS.META_FIELDS);
- const {
- indexPattern,
- indexPatternTitle,
- mappings,
- fieldDescriptors,
- } = await fetchIndexPatternDefinition(indexPatternId, context, metaFields);
+ const { indexPattern, indexPatternTitle } = await fetchIndexPatternDefinition(
+ indexPatternId,
+ context
+ );
- const fields = buildFieldList(indexPattern, mappings, fieldDescriptors, metaFields);
+ const fields = buildFieldList(indexPattern, metaFields);
const docs = await fetchIndexPatternStats({
fromDate,
toDate,
@@ -129,51 +108,17 @@ async function fetchFieldExistence({
};
}
-async function fetchIndexPatternDefinition(
- indexPatternId: string,
- context: RequestHandlerContext,
- metaFields: string[]
-) {
+async function fetchIndexPatternDefinition(indexPatternId: string, context: RequestHandlerContext) {
const savedObjectsClient = context.core.savedObjects.client;
- const requestClient = context.core.elasticsearch.legacy.client;
const indexPattern = await savedObjectsClient.get(
'index-pattern',
indexPatternId
);
const indexPatternTitle = indexPattern.attributes.title;
- if (indexPatternTitle.includes(':')) {
- // Cross cluster search patterns include a colon, and we aren't able to fetch
- // mapping information.
- return {
- indexPattern,
- indexPatternTitle,
- mappings: {},
- fieldDescriptors: [],
- };
- }
-
- // TODO: maybe don't use IndexPatternsFetcher at all, since we're only using it
- // to look up field values in the resulting documents. We can accomplish the same
- // using the mappings which we're also fetching here.
- const indexPatternsFetcher = new IndexPatternsFetcher(requestClient.callAsCurrentUser);
- const [mappings, fieldDescriptors] = await Promise.all([
- requestClient.callAsCurrentUser('indices.getMapping', {
- index: indexPatternTitle,
- }),
-
- indexPatternsFetcher.getFieldsForWildcard({
- pattern: indexPatternTitle,
- // TODO: Pull this from kibana advanced settings
- metaFields,
- }),
- ]);
-
return {
indexPattern,
indexPatternTitle,
- mappings,
- fieldDescriptors,
};
}
@@ -182,32 +127,13 @@ async function fetchIndexPatternDefinition(
*/
export function buildFieldList(
indexPattern: SavedObject,
- mappings: MappingResult | {},
- fieldDescriptors: FieldDescriptor[],
metaFields: string[]
): Field[] {
- const aliasMap = Object.entries(Object.values(mappings)[0]?.mappings.properties ?? {})
- .map(([name, v]) => ({ ...v, name }))
- .filter((f) => f.type === 'alias')
- .reduce((acc, f) => {
- acc[f.name] = f.path;
- return acc;
- }, {} as Record);
-
- const descriptorMap = fieldDescriptors.reduce((acc, f) => {
- acc[f.name] = f;
- return acc;
- }, {} as Record);
-
return JSON.parse(indexPattern.attributes.fields).map(
(field: { name: string; lang: string; scripted?: boolean; script?: string }) => {
- const path =
- aliasMap[field.name] || descriptorMap[field.name]?.subType?.multi?.parent || field.name;
return {
name: field.name,
isScript: !!field.scripted,
- isAlias: !!aliasMap[field.name],
- path: path.split('.'),
lang: field.lang,
script: field.script,
// id is a special case - it doesn't show up in the meta field list,
@@ -263,8 +189,8 @@ async function fetchIndexPatternStats({
size: SAMPLE_SIZE,
query,
sort: timeFieldName && fromDate && toDate ? [{ [timeFieldName]: 'desc' }] : [],
- // _source is required because we are also providing script fields.
- _source: '*',
+ fields: ['*'],
+ _source: false,
script_fields: scriptedFields.reduce((acc, field) => {
acc[field.name] = {
script: {
@@ -279,49 +205,11 @@ async function fetchIndexPatternStats({
return result.hits.hits;
}
-// Recursive function to determine if the _source of a document
-// contains a known path.
-function exists(obj: unknown, path: string[], i = 0): boolean {
- if (obj == null) {
- return false;
- }
-
- if (path.length === i) {
- return true;
- }
-
- if (Array.isArray(obj)) {
- return obj.some((child) => exists(child, path, i));
- }
-
- if (typeof obj === 'object') {
- // Because Elasticsearch flattens paths, dots in the field name are allowed
- // as JSON keys. For example, { 'a.b': 10 }
- const partialKeyMatches = Object.getOwnPropertyNames(obj)
- .map((key) => key.split('.'))
- .filter((keyPaths) => keyPaths.every((key, keyIndex) => key === path[keyIndex + i]));
-
- if (partialKeyMatches.length) {
- return partialKeyMatches.every((keyPaths) => {
- return exists(
- (obj as Record)[keyPaths.join('.')],
- path,
- i + keyPaths.length
- );
- });
- }
-
- return exists((obj as Record)[path[i]], path, i + 1);
- }
-
- return path.length === i;
-}
-
/**
* Exported only for unit tests.
*/
export function existingFields(
- docs: Array<{ _source: unknown; fields: unknown; [key: string]: unknown }>,
+ docs: Array<{ fields: Record; [key: string]: unknown }>,
fields: Field[]
): string[] {
const missingFields = new Set(fields);
@@ -332,14 +220,14 @@ export function existingFields(
}
missingFields.forEach((field) => {
- let fieldStore = doc._source;
- if (field.isScript) {
- fieldStore = doc.fields;
- }
+ let fieldStore: Record = doc.fields;
if (field.isMeta) {
fieldStore = doc;
}
- if (exists(fieldStore, field.path)) {
+ const value = fieldStore[field.name];
+ if (Array.isArray(value) && value.length) {
+ missingFields.delete(field);
+ } else if (!Array.isArray(value) && value) {
missingFields.delete(field);
}
});
diff --git a/x-pack/test/api_integration/apis/lens/existing_fields.ts b/x-pack/test/api_integration/apis/lens/existing_fields.ts
index 10ee7bc9b48ea..08806df380f38 100644
--- a/x-pack/test/api_integration/apis/lens/existing_fields.ts
+++ b/x-pack/test/api_integration/apis/lens/existing_fields.ts
@@ -22,7 +22,6 @@ const fieldsWithData = [
'@timestamp',
'_id',
'_index',
- '_source',
'agent',
'agent.raw',
'bytes',
@@ -60,6 +59,7 @@ const fieldsWithData = [
'utc_time',
'xss',
'xss.raw',
+ 'runtime_number',
'relatedContent.article:modified_time',
'relatedContent.article:published_time',
@@ -101,7 +101,6 @@ const metricBeatData = [
'@timestamp',
'_id',
'_index',
- '_source',
'agent.ephemeral_id',
'agent.hostname',
'agent.id',
@@ -193,7 +192,6 @@ export default ({ getService }: FtrProviderContext) => {
'@timestamp',
'_id',
'_index',
- '_source',
'agent',
'agent.raw',
'bytes',
@@ -211,6 +209,7 @@ export default ({ getService }: FtrProviderContext) => {
'request.raw',
'response',
'response.raw',
+ 'runtime_number',
'spaces',
'spaces.raw',
'type',
diff --git a/x-pack/test/functional/es_archives/logstash_functional/mappings.json b/x-pack/test/functional/es_archives/logstash_functional/mappings.json
index dcfafa2612c5b..ee7feedd77530 100644
--- a/x-pack/test/functional/es_archives/logstash_functional/mappings.json
+++ b/x-pack/test/functional/es_archives/logstash_functional/mappings.json
@@ -342,6 +342,11 @@
}
},
"type": "text"
+ },
+ "runtime_number": {
+ "type": "runtime",
+ "runtime_type" : "long",
+ "script" : { "source" : "emit(doc['bytes'].value)" }
}
}
},
@@ -707,6 +712,11 @@
}
},
"type": "text"
+ },
+ "runtime_number": {
+ "type": "runtime",
+ "runtime_type" : "long",
+ "script" : { "source" : "emit(doc['bytes'].value)" }
}
}
},
@@ -1072,6 +1082,11 @@
}
},
"type": "text"
+ },
+ "runtime_number": {
+ "type": "runtime",
+ "runtime_type" : "long",
+ "script" : { "source" : "emit(doc['bytes'].value)" }
}
}
},
diff --git a/x-pack/test/functional/es_archives/visualize/default/data.json b/x-pack/test/functional/es_archives/visualize/default/data.json
index f72a61c9e3b85..5b5ee355c7086 100644
--- a/x-pack/test/functional/es_archives/visualize/default/data.json
+++ b/x-pack/test/functional/es_archives/visualize/default/data.json
@@ -152,7 +152,7 @@
"index-pattern": {
"title": "logstash-*",
"timeFieldName": "@timestamp",
- "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]"
+ "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"runtime_number\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]"
},
"type": "index-pattern",
"migrationVersion": {
From 7cfdeaeedeb1bd81b436c6ec1b22e1f033f509d4 Mon Sep 17 00:00:00 2001
From: Ryland Herrick
Date: Fri, 2 Oct 2020 13:19:02 -0500
Subject: [PATCH 34/50] [Security Solution][Detections] EQL Validation (#77493)
* WIP: Adding new route for EQL Validation
This is mostly boilerplate with some rough parameter definitions; the
actual implementation of the validation is going to live in our
validateEql function.
A few tests are failing as the mocks haven't yet been implemented, I
need to see the shape of the responses first.
* Cherry-pick Marshall's EQL types
* Implements actual EQL validation
* Performs an EQL search
* filters out non-parsing errors, and returns what remains in the
response
* Adds mocks for empty EQL responses (we don't yet have a need for
mocked data, but we will when we unit-test validateEql)
* Adds validation calls to the EQL form input
* Adds EQL Validation response schema,mocks,tests
* Adds frontend function to call our validation endpoint
* Adds hook, useEqlValidation, to call the above function and return
state
* Adds labels/help text for EQL Query bar
* EqlQueryBar consumes useEqlValidation and marks the field as invalid,
but does not yet report errors.
* Do not call the validation API if query is not present
This causes a broader error that results in a 400 response; we can (and
do) handle the case of a blank query in the form itself.
* Remove EQL Help Text
It doesn't add any information for the user, and it currently looks bad
when combined with validation errors.
* Flesh out and use our popover for displaying validation errors
* Fixes issue where old errors were persisted after the user had made
modifications
* Include verification_exception errors as validation errors
These include errors related to index fields and mappings.
* Generalize our validation helpers
We're concerned with validation errors; the source of those errors is an
implementation detail of these functions.
* Move error popover and EQL reference link to footer
This more closely resembles the new Eui Markdown editor, which places
errors and doc links in a footer.
* Fix jest tests following additional prop
* Add icon for EQL Rule card
* Fixes existing EqlQueryBar tests
These were broken by our use of useAppToasts and the EUI theme.
* Add unit tests around error rendering on EQL Query Bar
* Add tests for ErrorPopover
* Remove unused schema type
Decode doesn't do any additional processing, so we can use t.TypeOf here
(the default for buildRouteValidation).
* Remove duplicated header
* Use ignore parameter to prevent EQL validations from logging errors
Without `ignore: [400]` the ES client will log errors and then throw
them. We can catch the error, but the log is undesirable.
This updates the query to use the ignore parameter, along with updating
the validation logic to work with the updated response.
Adds some mocks and tests around these responses and helpers, since
these will exist independent of the validation implementation.
* Include mapping_exceptions during EQL query validation
These include errors for inaccessible indexes, which should be useful to
the rule writer in writing their EQL query.
* Display toast messages for non-validation messages
* fix type errors
This type was renamed.
* Do not request data in our validation request
By not having the cluster retrieve/send any data, this should saves us
a few CPU cycles.
* Move EQL validation to an async form validator
Rather than invoking a custom validation hook (useEqlValidation) at custom times (onBlur) in our EqlQueryBar
component, we can instead move this functionality to a form validation
function and have it be invoked automatically by our form when values
change. However, because we still need to handle the validation messages
slightly differently (place them in a popover as opposed to an
EuiFormRow), we also need custom error retrieval in the form of
getValidationResults.
After much pain, it was determined that the default behavior of
_.debounce does not work with async validator functions, as a debounced
call will not "wait" for the eventual invocation but will instead return
the most recently resolved value. This leads to stale validation
results and terrible UX, so I wrote a custom function (debounceAsync)
that behaves like we want/need; see tests for details.
* Invalidate our query field when index patterns change
Since EQL rules actually validate against the relevant indexes, changing
said indexes should invalidate/revalidate the query.
With the form lib, this is beautifully simple :)
* Set a min-height on our EQL textarea
* Remove unused prop from EqlQueryBar
Index corresponds to the value from the index field; now that our EQL
validation is performed by the form we have no need for it here.
* Update EQL overview link to point to elasticsearch docs
Adds an entry in our doclinks service, and uses that.
* Remove unused prop from stale tests
* Update docLinks documentation with new EQL link
* Fix bug where saved query rules had no type selected on Edit
* Wait for kibana requests to complete before moving between rule tabs
With our new async validation, a user can quickly navigate away from the
Definition tab before the validation has completed, resulting in the
form being invalidated. Any subsequent user actions cause the form to
correct itself, but until I can find a better solution here this really
just gives the validation time to complete and sidesteps the issue.
---
...-plugin-core-public.doclinksstart.links.md | 1 +
...kibana-plugin-core-public.doclinksstart.md | 2 +-
.../public/doc_links/doc_links_service.ts | 2 +
src/core/public/public.api.md | 1 +
.../security_solution/common/constants.ts | 1 +
.../request/eql_validation_schema.mock.ts | 12 ++
.../request/eql_validation_schema.test.ts | 59 ++++++++++
.../schemas/request/eql_validation_schema.ts | 18 +++
.../response/eql_validation_schema.mock.ts | 17 +++
.../response/eql_validation_schema.test.ts | 59 ++++++++++
.../schemas/response/eql_validation_schema.ts | 16 +++
.../common/detection_engine/utils.ts | 3 +-
.../alerts_detection_rules_custom.spec.ts | 3 +-
.../cypress/screens/edit_rule.ts | 2 +
.../cypress/tasks/edit_rule.ts | 6 +-
.../public/common/hooks/eql/api.ts | 31 ++++++
.../rules/eql_query_bar/eql_overview_link.tsx | 26 +++++
.../eql_query_bar/eql_query_bar.test.tsx | 37 +++++-
.../rules/eql_query_bar/eql_query_bar.tsx | 53 +++++++--
.../eql_query_bar/errors_popover.test.tsx | 50 +++++++++
.../rules/eql_query_bar/errors_popover.tsx | 55 +++++++++
.../components/rules/eql_query_bar/footer.tsx | 42 +++++++
.../rules/eql_query_bar/translations.ts | 42 +++++++
.../rules/eql_query_bar/validators.mock.ts | 19 ++++
.../rules/eql_query_bar/validators.test.ts | 52 +++++++++
.../rules/eql_query_bar/validators.ts | 105 ++++++++++++++++++
.../select_rule_type/eql_search_icon.svg | 6 +
.../rules/select_rule_type/index.tsx | 5 +-
.../rules/step_define_rule/index.tsx | 5 +
.../rules/step_define_rule/schema.tsx | 13 +--
.../rules/step_define_rule/translations.tsx | 14 +++
.../public/shared_imports.ts | 1 +
.../routes/__mocks__/request_context.ts | 5 +-
.../routes/__mocks__/request_responses.ts | 27 ++++-
.../routes/eql/helpers.mock.ts | 69 ++++++++++++
.../routes/eql/helpers.test.ts | 58 ++++++++++
.../detection_engine/routes/eql/helpers.ts | 35 ++++++
.../routes/eql/validate_eql.ts | 46 ++++++++
.../routes/eql/validation_route.test.ts | 53 +++++++++
.../routes/eql/validation_route.ts | 49 ++++++++
.../security_solution/server/routes/index.ts | 4 +
41 files changed, 1075 insertions(+), 29 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.mock.ts
create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.test.ts
create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.ts
create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.mock.ts
create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.test.ts
create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.ts
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_overview_link.tsx
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_popover.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_popover.tsx
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.mock.ts
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts
create mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/eql_search_icon.svg
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.mock.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.test.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/validate_eql.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/validation_route.test.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/validation_route.ts
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
index f7b55b0650d8b..3afd5eaa6f1f7 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
@@ -91,6 +91,7 @@ readonly links: {
readonly gettingStarted: string;
};
readonly query: {
+ readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index 3f58cf08ee6b6..5249381969b98 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
@@ -17,5 +17,5 @@ export interface DocLinksStart
| --- | --- | --- |
| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string
| |
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string
| |
-| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
}
| |
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 47f58a3a9fcbf..629bf97c24887 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -119,6 +119,7 @@ export class DocLinksService {
gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,
},
query: {
+ eql: `${ELASTICSEARCH_DOCS}eql.html`,
luceneQuerySyntax: `${ELASTICSEARCH_DOCS}query-dsl-query-string-query.html#query-string-syntax`,
queryDsl: `${ELASTICSEARCH_DOCS}query-dsl.html`,
kueryQuerySyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kuery-query.html`,
@@ -227,6 +228,7 @@ export interface DocLinksStart {
readonly gettingStarted: string;
};
readonly query: {
+ readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 5970c9a8571c4..08491dc76cd27 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -539,6 +539,7 @@ export interface DocLinksStart {
readonly gettingStarted: string;
};
readonly query: {
+ readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index 2910f02a187f4..e46bd9e28d8c4 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -117,6 +117,7 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/p
export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`;
export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`;
export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`;
+export const DETECTION_ENGINE_EQL_VALIDATION_URL = `${DETECTION_ENGINE_URL}/validate_eql`;
export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`;
export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`;
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.mock.ts
new file mode 100644
index 0000000000000..96afc0c85df44
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.mock.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EqlValidationSchema } from './eql_validation_schema';
+
+export const getEqlValidationSchemaMock = (): EqlValidationSchema => ({
+ index: ['index-123'],
+ query: 'process where process.name == "regsvr32.exe"',
+});
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.test.ts
new file mode 100644
index 0000000000000..84bb8e067bf75
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.test.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { pipe } from 'fp-ts/lib/pipeable';
+import { left } from 'fp-ts/lib/Either';
+
+import { exactCheck } from '../../../exact_check';
+import { foldLeftRight, getPaths } from '../../../test_utils';
+import { eqlValidationSchema, EqlValidationSchema } from './eql_validation_schema';
+import { getEqlValidationSchemaMock } from './eql_validation_schema.mock';
+
+describe('EQL validation schema', () => {
+ it('requires a value for index', () => {
+ const payload = {
+ ...getEqlValidationSchemaMock(),
+ index: undefined,
+ };
+ const decoded = eqlValidationSchema.decode(payload);
+ const checked = exactCheck(payload, decoded);
+ const message = pipe(checked, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "undefined" supplied to "index"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ it('requires a value for query', () => {
+ const payload = {
+ ...getEqlValidationSchemaMock(),
+ query: undefined,
+ };
+ const decoded = eqlValidationSchema.decode(payload);
+ const checked = exactCheck(payload, decoded);
+ const message = pipe(checked, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "undefined" supplied to "query"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ it('validates a payload with index and query', () => {
+ const payload = getEqlValidationSchemaMock();
+ const decoded = eqlValidationSchema.decode(payload);
+ const checked = exactCheck(payload, decoded);
+ const message = pipe(checked, foldLeftRight);
+ const expected: EqlValidationSchema = {
+ index: ['index-123'],
+ query: 'process where process.name == "regsvr32.exe"',
+ };
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(expected);
+ });
+});
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.ts
new file mode 100644
index 0000000000000..abbbe33a32258
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/eql_validation_schema.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as t from 'io-ts';
+
+import { index, query } from '../common/schemas';
+
+export const eqlValidationSchema = t.exact(
+ t.type({
+ index,
+ query,
+ })
+);
+
+export type EqlValidationSchema = t.TypeOf;
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.mock.ts
new file mode 100644
index 0000000000000..98e5db47253fb
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.mock.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EqlValidationSchema } from './eql_validation_schema';
+
+export const getEqlValidationResponseMock = (): EqlValidationSchema => ({
+ valid: false,
+ errors: ['line 3:52: token recognition error at: '],
+});
+
+export const getValidEqlValidationResponseMock = (): EqlValidationSchema => ({
+ valid: true,
+ errors: [],
+});
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.test.ts
new file mode 100644
index 0000000000000..939238e340cff
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.test.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { pipe } from 'fp-ts/lib/pipeable';
+import { left } from 'fp-ts/lib/Either';
+
+import { exactCheck } from '../../../exact_check';
+import { foldLeftRight, getPaths } from '../../../test_utils';
+import { getEqlValidationResponseMock } from './eql_validation_schema.mock';
+import { eqlValidationSchema } from './eql_validation_schema';
+
+describe('EQL validation response schema', () => {
+ it('validates a typical response', () => {
+ const payload = getEqlValidationResponseMock();
+ const decoded = eqlValidationSchema.decode(payload);
+ const checked = exactCheck(payload, decoded);
+ const message = pipe(checked, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(getEqlValidationResponseMock());
+ });
+
+ it('invalidates a response with extra properties', () => {
+ const payload = { ...getEqlValidationResponseMock(), extra: 'nope' };
+ const decoded = eqlValidationSchema.decode(payload);
+ const checked = exactCheck(payload, decoded);
+ const message = pipe(checked, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['invalid keys "extra"']);
+ expect(message.schema).toEqual({});
+ });
+
+ it('invalidates a response with missing properties', () => {
+ const payload = { ...getEqlValidationResponseMock(), valid: undefined };
+ const decoded = eqlValidationSchema.decode(payload);
+ const checked = exactCheck(payload, decoded);
+ const message = pipe(checked, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "undefined" supplied to "valid"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ it('invalidates a response with properties of the wrong type', () => {
+ const payload = { ...getEqlValidationResponseMock(), errors: 'should be an array' };
+ const decoded = eqlValidationSchema.decode(payload);
+ const checked = exactCheck(payload, decoded);
+ const message = pipe(checked, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "should be an array" supplied to "errors"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+});
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.ts
new file mode 100644
index 0000000000000..e999e1dd273f8
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/eql_validation_schema.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as t from 'io-ts';
+
+export const eqlValidationSchema = t.exact(
+ t.type({
+ valid: t.boolean,
+ errors: t.array(t.string),
+ })
+);
+
+export type EqlValidationSchema = t.TypeOf;
diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
index f76417099bb17..d7b23755699f5 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
@@ -19,5 +19,6 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => {
export const isEqlRule = (ruleType: Type | undefined): boolean => ruleType === 'eql';
export const isThresholdRule = (ruleType: Type | undefined): boolean => ruleType === 'threshold';
-export const isQueryRule = (ruleType: Type | undefined): boolean => ruleType === 'query';
+export const isQueryRule = (ruleType: Type | undefined): boolean =>
+ ruleType === 'query' || ruleType === 'saved_query';
export const isThreatMatchRule = (ruleType: Type): boolean => ruleType === 'threat_match';
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
index f999c5cecc392..d8832dc4ee600 100644
--- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
@@ -93,7 +93,7 @@ import {
goToScheduleStepTab,
waitForTheRuleToBeExecuted,
} from '../tasks/create_new_rule';
-import { saveEditedRule } from '../tasks/edit_rule';
+import { saveEditedRule, waitForKibana } from '../tasks/edit_rule';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
import { refreshPage } from '../tasks/security_header';
@@ -290,6 +290,7 @@ describe('Custom detection rules deletion and edition', () => {
context('Edition', () => {
it('Allows a rule to be edited', () => {
editFirstRule();
+ waitForKibana();
// expect define step to populate
cy.get(CUSTOM_QUERY_INPUT).should('have.text', existingRule.customQuery);
diff --git a/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts
index 1bf0ff34ebd94..e25eb7453c63c 100644
--- a/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts
@@ -5,3 +5,5 @@
*/
export const EDIT_SUBMIT_BUTTON = '[data-test-subj="ruleEditSubmitButton"]';
+export const KIBANA_LOADING_INDICATOR = '[data-test-subj="globalLoadingIndicator"]';
+export const KIBANA_LOADING_COMPLETE_INDICATOR = '[data-test-subj="globalLoadingIndicator-hidden"]';
diff --git a/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts
index 690a36058ec33..2dc1318ccb81d 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts
@@ -4,9 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EDIT_SUBMIT_BUTTON } from '../screens/edit_rule';
+import { EDIT_SUBMIT_BUTTON, KIBANA_LOADING_COMPLETE_INDICATOR } from '../screens/edit_rule';
export const saveEditedRule = () => {
cy.get(EDIT_SUBMIT_BUTTON).should('exist').click({ force: true });
cy.get(EDIT_SUBMIT_BUTTON).should('not.exist');
};
+
+export const waitForKibana = () => {
+ cy.get(KIBANA_LOADING_COMPLETE_INDICATOR).should('exist');
+};
diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
new file mode 100644
index 0000000000000..11fe79910bc87
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { HttpStart } from '../../../../../../../src/core/public';
+import { DETECTION_ENGINE_EQL_VALIDATION_URL } from '../../../../common/constants';
+import { EqlValidationSchema as EqlValidationRequest } from '../../../../common/detection_engine/schemas/request/eql_validation_schema';
+import { EqlValidationSchema as EqlValidationResponse } from '../../../../common/detection_engine/schemas/response/eql_validation_schema';
+
+interface ApiParams {
+ http: HttpStart;
+ signal: AbortSignal;
+}
+
+export const validateEql = async ({
+ http,
+ query,
+ index,
+ signal,
+}: ApiParams & EqlValidationRequest) => {
+ return http.fetch(DETECTION_ENGINE_EQL_VALIDATION_URL, {
+ method: 'POST',
+ body: JSON.stringify({
+ query,
+ index,
+ }),
+ signal,
+ });
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_overview_link.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_overview_link.tsx
new file mode 100644
index 0000000000000..e9891fc066ec2
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_overview_link.tsx
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import styled from 'styled-components';
+import { EuiLink, EuiText } from '@elastic/eui';
+
+import { useKibana } from '../../../../common/lib/kibana';
+import { EQL_OVERVIEW_LINK_TEXT } from './translations';
+
+const InlineText = styled(EuiText)`
+ display: inline-block;
+`;
+
+export const EqlOverviewLink = () => {
+ const overviewUrl = useKibana().services.docLinks.links.query.eql;
+
+ return (
+
+ {EQL_OVERVIEW_LINK_TEXT}
+
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx
index 331c0ba4c4491..5539e5eb2c294 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx
@@ -7,9 +7,12 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
-import { useFormFieldMock } from '../../../../common/mock';
+import { TestProviders, useFormFieldMock } from '../../../../common/mock';
import { mockQueryBar } from '../../../pages/detection_engine/rules/all/__mocks__/mock';
import { EqlQueryBar, EqlQueryBarProps } from './eql_query_bar';
+import { getEqlValidationError } from './validators.mock';
+
+jest.mock('../../../../common/lib/kibana');
describe('EqlQueryBar', () => {
let mockField: EqlQueryBarProps['field'];
@@ -27,7 +30,11 @@ describe('EqlQueryBar', () => {
});
it('sets the field value on input change', () => {
- const wrapper = mount( );
+ const wrapper = mount(
+
+
+
+ );
wrapper
.find('[data-test-subj="eqlQueryBarTextInput"]')
@@ -44,4 +51,30 @@ describe('EqlQueryBar', () => {
expect(mockField.setValue).toHaveBeenCalledWith(expected);
});
+
+ it('does not render errors for a valid query', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="eql-validation-errors-popover"]').exists()).toEqual(
+ false
+ );
+ });
+
+ it('renders errors for an invalid query', () => {
+ const invalidMockField = useFormFieldMock({
+ value: mockQueryBar,
+ errors: [getEqlValidationError()],
+ });
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="eql-validation-errors-popover"]').exists()).toEqual(true);
+ });
});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
index e3f33ea9b9b87..f7ee5be18154c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
@@ -4,11 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC, useCallback, ChangeEvent } from 'react';
+import React, { FC, useCallback, ChangeEvent, useEffect, useState } from 'react';
+import styled from 'styled-components';
import { EuiFormRow, EuiTextArea } from '@elastic/eui';
-import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { FieldHook } from '../../../../shared_imports';
+import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { DefineStepRule } from '../../../pages/detection_engine/rules/types';
+import * as i18n from './translations';
+import { EqlQueryBarFooter } from './footer';
+import { getValidationResults } from './validators';
+
+const TextArea = styled(EuiTextArea)`
+ display: block;
+ border: ${({ theme }) => theme.eui.euiBorderThin};
+ border-bottom: 0;
+ box-shadow: none;
+ min-height: ${({ theme }) => theme.eui.euiFormControlHeight};
+`;
export interface EqlQueryBarProps {
dataTestSubj: string;
@@ -17,14 +30,27 @@ export interface EqlQueryBarProps {
}
export const EqlQueryBar: FC = ({ dataTestSubj, field, idAria }) => {
+ const { addError } = useAppToasts();
+ const [errorMessages, setErrorMessages] = useState([]);
const { setValue } = field;
- const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
+ const { isValid, message, messages, error } = getValidationResults(field);
const fieldValue = field.value.query.query as string;
+ useEffect(() => {
+ setErrorMessages(messages ?? []);
+ }, [messages]);
+
+ useEffect(() => {
+ if (error) {
+ addError(error, { title: i18n.EQL_VALIDATION_REQUEST_ERROR });
+ }
+ }, [error, addError]);
+
const handleChange = useCallback(
(e: ChangeEvent) => {
const newQuery = e.target.value;
+ setErrorMessages([]);
setValue({
filters: [],
query: {
@@ -41,19 +67,22 @@ export const EqlQueryBar: FC = ({ dataTestSubj, field, idAria
label={field.label}
labelAppend={field.labelAppend}
helpText={field.helpText}
- error={errorMessage}
- isInvalid={isInvalid}
+ error={message}
+ isInvalid={!isValid}
fullWidth
data-test-subj={dataTestSubj}
describedByIds={idAria ? [idAria] : undefined}
>
-
+ <>
+
+
+ >
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_popover.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_popover.test.tsx
new file mode 100644
index 0000000000000..01bd8afd0e4ab
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_popover.test.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+
+import { ErrorsPopover } from './errors_popover';
+
+describe('ErrorsPopover', () => {
+ let mockErrors: string[];
+
+ beforeEach(() => {
+ mockErrors = [];
+ });
+
+ it('renders correctly', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find('[data-test-subj="eql-validation-errors-popover"]')).toHaveLength(1);
+ });
+
+ it('renders the number of errors by default', () => {
+ mockErrors = ['error', 'other', 'third'];
+ const wrapper = mount( );
+ expect(
+ wrapper.find('[data-test-subj="eql-validation-errors-popover"]').first().text()
+ ).toContain('3');
+ });
+
+ it('renders the error messages if clicked', () => {
+ mockErrors = ['error', 'other'];
+ const wrapper = mount( );
+ wrapper
+ .find('[data-test-subj="eql-validation-errors-popover-button"]')
+ .first()
+ .simulate('click');
+
+ expect(
+ wrapper.find('[data-test-subj="eql-validation-errors-popover"]').first().text()
+ ).toContain('2');
+ const messagesContent = wrapper
+ .find('[data-test-subj="eql-validation-errors-popover-content"]')
+ .text();
+ expect(messagesContent).toContain('error');
+ expect(messagesContent).toContain('other');
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_popover.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_popover.tsx
new file mode 100644
index 0000000000000..a7122b7dc65d8
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/errors_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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, useCallback, useState } from 'react';
+import { EuiButtonEmpty, EuiPopover, EuiPopoverTitle, EuiText } from '@elastic/eui';
+
+import * as i18n from './translations';
+
+export interface ErrorsPopoverProps {
+ ariaLabel?: string;
+ errors: string[];
+}
+
+export const ErrorsPopover: FC = ({ ariaLabel, errors }) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const handleToggle = useCallback(() => {
+ setIsOpen(!isOpen);
+ }, [isOpen]);
+
+ const handleClose = useCallback(() => {
+ setIsOpen(false);
+ }, []);
+
+ return (
+
+ {errors.length}
+
+ }
+ isOpen={isOpen}
+ closePopover={handleClose}
+ anchorPosition="downCenter"
+ >
+
+ {i18n.EQL_VALIDATION_ERRORS_TITLE}
+ {errors.map((message, idx) => (
+ {message}
+ ))}
+
+
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx
new file mode 100644
index 0000000000000..19bab26f8aa58
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import styled from 'styled-components';
+import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
+
+import * as i18n from './translations';
+import { ErrorsPopover } from './errors_popover';
+import { EqlOverviewLink } from './eql_overview_link';
+
+export interface Props {
+ errors: string[];
+}
+
+const Container = styled(EuiPanel)`
+ border-radius: 0;
+ background: ${({ theme }) => theme.eui.euiPageBackgroundColor};
+ padding: ${({ theme }) => theme.eui.euiSizeXS};
+`;
+
+const FlexGroup = styled(EuiFlexGroup)`
+ min-height: ${({ theme }) => theme.eui.euiSizeXL};
+`;
+
+export const EqlQueryBarFooter: FC = ({ errors }) => (
+
+
+
+ {errors.length > 0 && (
+
+ )}
+
+
+
+
+
+
+);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.ts
new file mode 100644
index 0000000000000..8b016d9ad68cb
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const EQL_VALIDATION_REQUEST_ERROR = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlValidation.requestError',
+ {
+ defaultMessage: 'An error occurred while validating your EQL query',
+ }
+);
+
+export const EQL_VALIDATION_ERRORS_TITLE = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlValidation.title',
+ {
+ defaultMessage: 'EQL Validation Errors',
+ }
+);
+
+export const EQL_VALIDATION_ERROR_POPOVER_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlValidation.showErrorsLabel',
+ {
+ defaultMessage: 'Show EQL Validation Errors',
+ }
+);
+
+export const EQL_QUERY_BAR_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlQueryBar.label',
+ {
+ defaultMessage: 'Enter an EQL Query',
+ }
+);
+
+export const EQL_OVERVIEW_LINK_TEXT = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOverViewLink.text',
+ {
+ defaultMessage: 'Event Query Language (EQL) Overview',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.mock.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.mock.ts
new file mode 100644
index 0000000000000..40e45b9c2d470
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.mock.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ValidationError } from '../../../../shared_imports';
+import { ERROR_CODES } from './validators';
+
+export const getEqlResponseError = (): ValidationError => ({
+ code: ERROR_CODES.FAILED_REQUEST,
+ message: 'something went wrong',
+});
+
+export const getEqlValidationError = (): ValidationError => ({
+ code: ERROR_CODES.INVALID_EQL,
+ messages: ['line 1: WRONG\nline 2: ALSO WRONG'],
+ message: '',
+});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts
new file mode 100644
index 0000000000000..24afce7bb18b0
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { debounceAsync } from './validators';
+
+jest.useFakeTimers();
+
+describe('debounceAsync', () => {
+ let fn: jest.Mock;
+
+ beforeEach(() => {
+ fn = jest.fn().mockResolvedValueOnce('first');
+ });
+
+ it('resolves with the underlying invocation result', async () => {
+ const debounced = debounceAsync(fn, 0);
+ const promise = debounced();
+ jest.runOnlyPendingTimers();
+
+ expect(await promise).toEqual('first');
+ });
+
+ it('resolves intermediate calls when the next invocation resolves', async () => {
+ const debounced = debounceAsync(fn, 200);
+ fn.mockResolvedValueOnce('second');
+
+ const promise = debounced();
+ jest.runOnlyPendingTimers();
+ expect(await promise).toEqual('first');
+
+ const promises = [debounced(), debounced()];
+ jest.runOnlyPendingTimers();
+
+ expect(await Promise.all(promises)).toEqual(['second', 'second']);
+ });
+
+ it('debounces the function', async () => {
+ const debounced = debounceAsync(fn, 200);
+
+ debounced();
+ jest.runOnlyPendingTimers();
+
+ debounced();
+ debounced();
+ jest.runOnlyPendingTimers();
+
+ expect(fn).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts
new file mode 100644
index 0000000000000..165522c10916e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { isEmpty } from 'lodash';
+
+import { FieldHook, ValidationError, ValidationFunc } from '../../../../shared_imports';
+import { isEqlRule } from '../../../../../common/detection_engine/utils';
+import { KibanaServices } from '../../../../common/lib/kibana';
+import { DefineStepRule } from '../../../pages/detection_engine/rules/types';
+import { validateEql } from '../../../../common/hooks/eql/api';
+import { FieldValueQueryBar } from '../query_bar';
+import * as i18n from './translations';
+
+export enum ERROR_CODES {
+ FAILED_REQUEST = 'ERR_FAILED_REQUEST',
+ INVALID_EQL = 'ERR_INVALID_EQL',
+}
+
+/**
+ * Unlike lodash's debounce, which resolves intermediate calls with the most
+ * recent value, this implementation waits to resolve intermediate calls until
+ * the next invocation resolves.
+ *
+ * @param fn an async function
+ *
+ * @returns A debounced async function that resolves on the next invocation
+ */
+export const debounceAsync = >(
+ fn: (...args: Args) => Result,
+ interval: number
+): ((...args: Args) => Result) => {
+ let handle: ReturnType | undefined;
+ let resolves: Array<(value?: Result) => void> = [];
+
+ return (...args: Args): Result => {
+ if (handle) {
+ clearTimeout(handle);
+ }
+
+ handle = setTimeout(() => {
+ const result = fn(...args);
+ resolves.forEach((resolve) => resolve(result));
+ resolves = [];
+ }, interval);
+
+ return new Promise((resolve) => resolves.push(resolve)) as Result;
+ };
+};
+
+export const eqlValidator = async (
+ ...args: Parameters
+): Promise | void | undefined> => {
+ const [{ value, formData }] = args;
+ const { query: queryValue } = value as FieldValueQueryBar;
+ const query = queryValue.query as string;
+ const { index, ruleType } = formData as DefineStepRule;
+
+ const needsValidation = isEqlRule(ruleType) && !isEmpty(query);
+ if (!needsValidation) {
+ return;
+ }
+
+ try {
+ const { http } = KibanaServices.get();
+ const signal = new AbortController().signal;
+ const response = await validateEql({ query, http, signal, index });
+
+ if (response?.valid === false) {
+ return {
+ code: ERROR_CODES.INVALID_EQL,
+ message: '',
+ messages: response.errors,
+ };
+ }
+ } catch (error) {
+ return {
+ code: ERROR_CODES.FAILED_REQUEST,
+ message: i18n.EQL_VALIDATION_REQUEST_ERROR,
+ error,
+ };
+ }
+};
+
+export const getValidationResults = (
+ field: FieldHook
+): { isValid: boolean; message: string; messages?: string[]; error?: Error } => {
+ const hasErrors = field.errors.length > 0;
+ const isValid = !field.isChangingValue && !hasErrors;
+
+ if (hasErrors) {
+ const [error] = field.errors;
+ const message = error.message;
+
+ if (error.code === ERROR_CODES.INVALID_EQL) {
+ return { isValid, message, messages: error.messages };
+ } else {
+ return { isValid, message, error: error.error };
+ }
+ } else {
+ return { isValid, message: '' };
+ }
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/eql_search_icon.svg b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/eql_search_icon.svg
new file mode 100644
index 0000000000000..716fff726293c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/eql_search_icon.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx
index 9a1d11a2dfe42..4b96b8a0ad7bd 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx
@@ -7,6 +7,7 @@
import React, { useCallback, useMemo } from 'react';
import { EuiCard, EuiFlexGrid, EuiFlexItem, EuiFormRow, EuiIcon } from '@elastic/eui';
+import { Type } from '../../../../../common/detection_engine/schemas/common/schemas';
import { isMlRule } from '../../../../../common/machine_learning/helpers';
import {
isThresholdRule,
@@ -18,7 +19,7 @@ import { FieldHook } from '../../../../shared_imports';
import { useKibana } from '../../../../common/lib/kibana';
import * as i18n from './translations';
import { MlCardDescription } from './ml_card_description';
-import { Type } from '../../../../../common/detection_engine/schemas/common/schemas';
+import EqlSearchIcon from './eql_search_icon.svg';
interface SelectRuleTypeProps {
describedByIds?: string[];
@@ -144,7 +145,7 @@ export const SelectRuleType: React.FC = ({
data-test-subj="eqlRuleType"
title={i18n.EQL_TYPE_TITLE}
description={i18n.EQL_TYPE_DESCRIPTION}
- icon={ }
+ icon={ }
isDisabled={eqlSelectableConfig.isDisabled && !eqlSelectableConfig.isSelected}
selectable={eqlSelectableConfig}
/>
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
index f728a508fef86..9bd0e4fb4da5d 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
@@ -274,6 +274,10 @@ const StepDefineRuleComponent: FC = ({
isLoading: indexPatternsLoading,
dataTestSubj: 'detectionEngineStepDefineRuleEqlQueryBar',
}}
+ config={{
+ ...schema.queryBar,
+ label: i18n.EQL_QUERY_BAR_LABEL,
+ }}
/>
) : (
= ({
path="queryBar"
config={{
...schema.queryBar,
+ label: i18n.QUERY_BAR_LABEL,
labelAppend: (
= {
index: {
+ fieldsToValidateOnChange: ['index', 'queryBar'],
type: FIELD_TYPES.COMBO_BOX,
label: i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fiedIndexPatternsLabel',
@@ -69,12 +71,6 @@ export const schema: FormSchema = {
],
},
queryBar: {
- label: i18n.translate(
- 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldQuerBarLabel',
- {
- defaultMessage: 'Custom query',
- }
- ),
validations: [
{
validator: (
@@ -120,6 +116,9 @@ export const schema: FormSchema = {
}
},
},
+ {
+ validator: debounceAsync(eqlValidator, 300),
+ },
],
},
ruleType: {
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx
index 8e0a3f9b8659e..164b1df8463e6 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx
@@ -71,6 +71,20 @@ export const ENABLE_ML_JOB_WARNING = i18n.translate(
}
);
+export const QUERY_BAR_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldQuerBarLabel',
+ {
+ defaultMessage: 'Custom query',
+ }
+);
+
+export const EQL_QUERY_BAR_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel',
+ {
+ defaultMessage: 'EQL query',
+ }
+);
+
export const THREAT_MATCH_INDEX_HELPER_TEXT = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchingIcesHelperDescription',
{
diff --git a/x-pack/plugins/security_solution/public/shared_imports.ts b/x-pack/plugins/security_solution/public/shared_imports.ts
index 08e9fb854e5a2..f60fefc4f6dfa 100644
--- a/x-pack/plugins/security_solution/public/shared_imports.ts
+++ b/x-pack/plugins/security_solution/public/shared_imports.ts
@@ -22,6 +22,7 @@ export {
useForm,
useFormContext,
useFormData,
+ ValidationError,
ValidationFunc,
VALIDATION_TYPES,
} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts
index c45dd5bd8a281..8e379e5caa89e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts
@@ -18,6 +18,7 @@ const createMockClients = () => ({
alertsClient: alertsClientMock.create(),
clusterClient: elasticsearchServiceMock.createLegacyScopedClusterClient(),
licensing: { license: licensingMock.createLicenseMock() },
+ newClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
savedObjectsClient: savedObjectsClientMock.create(),
appClient: siemMock.createClient(),
});
@@ -31,7 +32,9 @@ const createRequestContextMock = (
core: {
...coreContext,
elasticsearch: {
- legacy: { ...coreContext.elasticsearch, client: clients.clusterClient },
+ ...coreContext.elasticsearch,
+ client: clients.newClusterClient,
+ legacy: { ...coreContext.elasticsearch.legacy, client: clients.clusterClient },
},
savedObjects: { client: clients.savedObjectsClient },
},
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts
index 894ac2e0bb703..33da5a0c2322d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts
@@ -15,8 +15,9 @@ import {
INTERNAL_RULE_ID_KEY,
INTERNAL_IMMUTABLE_KEY,
DETECTION_ENGINE_PREPACKAGED_URL,
+ DETECTION_ENGINE_EQL_VALIDATION_URL,
} from '../../../../../common/constants';
-import { ShardsResponse } from '../../../types';
+import { EqlSearchResponse, ShardsResponse } from '../../../types';
import {
RuleAlertType,
IRuleSavedAttributesSavedObjectAttributes,
@@ -28,6 +29,7 @@ import { QuerySignalsSchemaDecoded } from '../../../../../common/detection_engin
import { SetSignalsStatusSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema';
import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock';
import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock';
+import { getEqlValidationSchemaMock } from '../../../../../common/detection_engine/schemas/request/eql_validation_schema.mock';
export const typicalSetStatusSignalByIdsPayload = (): SetSignalsStatusSchemaDecoded => ({
signal_ids: ['somefakeid1', 'somefakeid2'],
@@ -145,6 +147,13 @@ export const getPrepackagedRulesStatusRequest = () =>
path: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`,
});
+export const eqlValidationRequest = () =>
+ requestMock.create({
+ method: 'post',
+ path: DETECTION_ENGINE_EQL_VALIDATION_URL,
+ body: getEqlValidationSchemaMock(),
+ });
+
export interface FindHit {
page: number;
perPage: number;
@@ -577,6 +586,22 @@ export const getEmptySignalsResponse = (): SignalSearchResponse => ({
},
});
+export const getEmptyEqlSearchResponse = (): EqlSearchResponse => ({
+ hits: { total: { value: 0, relation: 'eq' }, events: [] },
+ is_partial: false,
+ is_running: false,
+ took: 1,
+ timed_out: false,
+});
+
+export const getEmptyEqlSequencesResponse = (): EqlSearchResponse => ({
+ hits: { total: { value: 0, relation: 'eq' }, sequences: [] },
+ is_partial: false,
+ is_running: false,
+ took: 1,
+ timed_out: false,
+});
+
export const getSuccessfulSignalUpdateResponse = () => ({
took: 18,
timed_out: false,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.mock.ts
new file mode 100644
index 0000000000000..4aa4c38802a92
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.mock.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ApiResponse } from '@elastic/elasticsearch';
+import { ErrorResponse } from './helpers';
+
+export const getValidEqlResponse = (): ApiResponse['body'] => ({
+ is_partial: false,
+ is_running: false,
+ took: 162,
+ timed_out: false,
+ hits: {
+ total: {
+ value: 1,
+ relation: 'eq',
+ },
+ sequences: [],
+ },
+});
+
+export const getEqlResponseWithValidationError = (): ErrorResponse => ({
+ error: {
+ root_cause: [
+ {
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+ ],
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+});
+
+export const getEqlResponseWithValidationErrors = (): ErrorResponse => ({
+ error: {
+ root_cause: [
+ {
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+ {
+ type: 'parsing_exception',
+ reason: "line 1:4: mismatched input '' expecting 'where'",
+ },
+ ],
+ type: 'verification_exception',
+ reason:
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ },
+});
+
+export const getEqlResponseWithNonValidationError = (): ApiResponse['body'] => ({
+ error: {
+ root_cause: [
+ {
+ type: 'other_error',
+ reason: 'some other reason',
+ },
+ ],
+ type: 'other_error',
+ reason: 'some other reason',
+ },
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.test.ts
new file mode 100644
index 0000000000000..41a1ef0faf69f
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.test.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getValidationErrors, isErrorResponse, isValidationErrorResponse } from './helpers';
+import {
+ getEqlResponseWithNonValidationError,
+ getEqlResponseWithValidationError,
+ getEqlResponseWithValidationErrors,
+ getValidEqlResponse,
+} from './helpers.mock';
+
+describe('eql validation helpers', () => {
+ describe('isErrorResponse', () => {
+ it('is false for a regular response', () => {
+ expect(isErrorResponse(getValidEqlResponse())).toEqual(false);
+ });
+
+ it('is true for a response with non-validation errors', () => {
+ expect(isErrorResponse(getEqlResponseWithNonValidationError())).toEqual(true);
+ });
+
+ it('is true for a response with validation errors', () => {
+ expect(isErrorResponse(getEqlResponseWithValidationError())).toEqual(true);
+ });
+ });
+
+ describe('isValidationErrorResponse', () => {
+ it('is false for a regular response', () => {
+ expect(isValidationErrorResponse(getValidEqlResponse())).toEqual(false);
+ });
+
+ it('is false for a response with non-validation errors', () => {
+ expect(isValidationErrorResponse(getEqlResponseWithNonValidationError())).toEqual(false);
+ });
+
+ it('is true for a response with validation errors', () => {
+ expect(isValidationErrorResponse(getEqlResponseWithValidationError())).toEqual(true);
+ });
+ });
+
+ describe('getValidationErrors', () => {
+ it('returns a single error for a single root cause', () => {
+ expect(getValidationErrors(getEqlResponseWithValidationError())).toEqual([
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ ]);
+ });
+
+ it('returns multiple errors for multiple root causes', () => {
+ expect(getValidationErrors(getEqlResponseWithValidationErrors())).toEqual([
+ 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
+ "line 1:4: mismatched input '' expecting 'where'",
+ ]);
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.ts
new file mode 100644
index 0000000000000..71601574802ce
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/helpers.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import get from 'lodash/get';
+import has from 'lodash/has';
+
+const PARSING_ERROR_TYPE = 'parsing_exception';
+const VERIFICATION_ERROR_TYPE = 'verification_exception';
+const MAPPING_ERROR_TYPE = 'mapping_exception';
+
+interface ErrorCause {
+ type: string;
+ reason: string;
+}
+
+export interface ErrorResponse {
+ error: ErrorCause & { root_cause: ErrorCause[] };
+}
+
+const isValidationErrorType = (type: unknown): boolean =>
+ type === PARSING_ERROR_TYPE || type === VERIFICATION_ERROR_TYPE || type === MAPPING_ERROR_TYPE;
+
+export const isErrorResponse = (response: unknown): response is ErrorResponse =>
+ has(response, 'error.type');
+
+export const isValidationErrorResponse = (response: unknown): response is ErrorResponse =>
+ isErrorResponse(response) && isValidationErrorType(get(response, 'error.type'));
+
+export const getValidationErrors = (response: ErrorResponse): string[] =>
+ response.error.root_cause
+ .filter((cause) => isValidationErrorType(cause.type))
+ .map((cause) => cause.reason);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/validate_eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/validate_eql.ts
new file mode 100644
index 0000000000000..ab3bbc7b06cc9
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/eql/validate_eql.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ElasticsearchClient } from '../../../../../../../../src/core/server';
+import { getValidationErrors, isErrorResponse, isValidationErrorResponse } from './helpers';
+
+export interface Validation {
+ isValid: boolean;
+ errors: string[];
+}
+
+export interface ValidateEqlParams {
+ client: ElasticsearchClient;
+ index: string[];
+ query: string;
+}
+
+export const validateEql = async ({
+ client,
+ index,
+ query,
+}: ValidateEqlParams): Promise